##// END OF EJS Templates
Merge pull request #4897 from khinsen/kernel_cmd_doc...
Min RK -
r14822:be63e098 merge
parent child Browse files
Show More
@@ -1,385 +1,391 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
4 # Copyright (C) 2013 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 # Standard library imports
16 # Standard library imports
17 import re
17 import re
18 import signal
18 import signal
19 import sys
19 import sys
20 import time
20 import time
21
21
22 import zmq
22 import zmq
23
23
24 # Local imports
24 # Local imports
25 from IPython.config.configurable import LoggingConfigurable
25 from IPython.config.configurable import LoggingConfigurable
26 from IPython.utils.importstring import import_item
26 from IPython.utils.importstring import import_item
27 from IPython.utils.localinterfaces import is_local_ip, local_ips
27 from IPython.utils.localinterfaces import is_local_ip, local_ips
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
29 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
30 )
30 )
31 from IPython.kernel import (
31 from IPython.kernel import (
32 make_ipkernel_cmd,
32 make_ipkernel_cmd,
33 launch_kernel,
33 launch_kernel,
34 )
34 )
35 from .connect import ConnectionFileMixin
35 from .connect import ConnectionFileMixin
36 from .zmq.session import Session
36 from .zmq.session import Session
37 from .managerabc import (
37 from .managerabc import (
38 KernelManagerABC
38 KernelManagerABC
39 )
39 )
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Main kernel manager class
42 # Main kernel manager class
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
45 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
46 """Manages a single kernel in a subprocess on this host.
46 """Manages a single kernel in a subprocess on this host.
47
47
48 This version starts kernels with Popen.
48 This version starts kernels with Popen.
49 """
49 """
50
50
51 # The PyZMQ Context to use for communication with the kernel.
51 # The PyZMQ Context to use for communication with the kernel.
52 context = Instance(zmq.Context)
52 context = Instance(zmq.Context)
53 def _context_default(self):
53 def _context_default(self):
54 return zmq.Context.instance()
54 return zmq.Context.instance()
55
55
56 # The Session to use for communication with the kernel.
56 # The Session to use for communication with the kernel.
57 session = Instance(Session)
57 session = Instance(Session)
58 def _session_default(self):
58 def _session_default(self):
59 return Session(parent=self)
59 return Session(parent=self)
60
60
61 # the class to create with our `client` method
61 # the class to create with our `client` method
62 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
62 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
63 client_factory = Type()
63 client_factory = Type()
64 def _client_class_changed(self, name, old, new):
64 def _client_class_changed(self, name, old, new):
65 self.client_factory = import_item(str(new))
65 self.client_factory = import_item(str(new))
66
66
67 # The kernel process with which the KernelManager is communicating.
67 # The kernel process with which the KernelManager is communicating.
68 # generally a Popen instance
68 # generally a Popen instance
69 kernel = Any()
69 kernel = Any()
70
70
71 kernel_cmd = List(Unicode, config=True,
71 kernel_cmd = List(Unicode, config=True,
72 help="""The Popen Command to launch the kernel.
72 help="""The Popen Command to launch the kernel.
73 Override this if you have a custom
73 Override this if you have a custom kernel.
74 If kernel_cmd is specified in a configuration file,
75 IPython does not pass any arguments to the kernel,
76 because it cannot make any assumptions about the
77 arguments that the kernel understands. In particular,
78 this means that the kernel does not receive the
79 option --debug if it given on the IPython command line.
74 """
80 """
75 )
81 )
76
82
77 def _kernel_cmd_changed(self, name, old, new):
83 def _kernel_cmd_changed(self, name, old, new):
78 self.ipython_kernel = False
84 self.ipython_kernel = False
79
85
80 ipython_kernel = Bool(True)
86 ipython_kernel = Bool(True)
81
87
82 # Protected traits
88 # Protected traits
83 _launch_args = Any()
89 _launch_args = Any()
84 _control_socket = Any()
90 _control_socket = Any()
85
91
86 _restarter = Any()
92 _restarter = Any()
87
93
88 autorestart = Bool(False, config=True,
94 autorestart = Bool(False, config=True,
89 help="""Should we autorestart the kernel if it dies."""
95 help="""Should we autorestart the kernel if it dies."""
90 )
96 )
91
97
92 def __del__(self):
98 def __del__(self):
93 self._close_control_socket()
99 self._close_control_socket()
94 self.cleanup_connection_file()
100 self.cleanup_connection_file()
95
101
96 #--------------------------------------------------------------------------
102 #--------------------------------------------------------------------------
97 # Kernel restarter
103 # Kernel restarter
98 #--------------------------------------------------------------------------
104 #--------------------------------------------------------------------------
99
105
100 def start_restarter(self):
106 def start_restarter(self):
101 pass
107 pass
102
108
103 def stop_restarter(self):
109 def stop_restarter(self):
104 pass
110 pass
105
111
106 def add_restart_callback(self, callback, event='restart'):
112 def add_restart_callback(self, callback, event='restart'):
107 """register a callback to be called when a kernel is restarted"""
113 """register a callback to be called when a kernel is restarted"""
108 if self._restarter is None:
114 if self._restarter is None:
109 return
115 return
110 self._restarter.add_callback(callback, event)
116 self._restarter.add_callback(callback, event)
111
117
112 def remove_restart_callback(self, callback, event='restart'):
118 def remove_restart_callback(self, callback, event='restart'):
113 """unregister a callback to be called when a kernel is restarted"""
119 """unregister a callback to be called when a kernel is restarted"""
114 if self._restarter is None:
120 if self._restarter is None:
115 return
121 return
116 self._restarter.remove_callback(callback, event)
122 self._restarter.remove_callback(callback, event)
117
123
118 #--------------------------------------------------------------------------
124 #--------------------------------------------------------------------------
119 # create a Client connected to our Kernel
125 # create a Client connected to our Kernel
120 #--------------------------------------------------------------------------
126 #--------------------------------------------------------------------------
121
127
122 def client(self, **kwargs):
128 def client(self, **kwargs):
123 """Create a client configured to connect to our kernel"""
129 """Create a client configured to connect to our kernel"""
124 if self.client_factory is None:
130 if self.client_factory is None:
125 self.client_factory = import_item(self.client_class)
131 self.client_factory = import_item(self.client_class)
126
132
127 kw = {}
133 kw = {}
128 kw.update(self.get_connection_info())
134 kw.update(self.get_connection_info())
129 kw.update(dict(
135 kw.update(dict(
130 connection_file=self.connection_file,
136 connection_file=self.connection_file,
131 session=self.session,
137 session=self.session,
132 parent=self,
138 parent=self,
133 ))
139 ))
134
140
135 # add kwargs last, for manual overrides
141 # add kwargs last, for manual overrides
136 kw.update(kwargs)
142 kw.update(kwargs)
137 return self.client_factory(**kw)
143 return self.client_factory(**kw)
138
144
139 #--------------------------------------------------------------------------
145 #--------------------------------------------------------------------------
140 # Kernel management
146 # Kernel management
141 #--------------------------------------------------------------------------
147 #--------------------------------------------------------------------------
142
148
143 def format_kernel_cmd(self, **kw):
149 def format_kernel_cmd(self, **kw):
144 """replace templated args (e.g. {connection_file})"""
150 """replace templated args (e.g. {connection_file})"""
145 if self.kernel_cmd:
151 if self.kernel_cmd:
146 cmd = self.kernel_cmd
152 cmd = self.kernel_cmd
147 else:
153 else:
148 cmd = make_ipkernel_cmd(
154 cmd = make_ipkernel_cmd(
149 'from IPython.kernel.zmq.kernelapp import main; main()',
155 'from IPython.kernel.zmq.kernelapp import main; main()',
150 **kw
156 **kw
151 )
157 )
152 ns = dict(connection_file=self.connection_file)
158 ns = dict(connection_file=self.connection_file)
153 ns.update(self._launch_args)
159 ns.update(self._launch_args)
154
160
155 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
161 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
156 def from_ns(match):
162 def from_ns(match):
157 """Get the key out of ns if it's there, otherwise no change."""
163 """Get the key out of ns if it's there, otherwise no change."""
158 return ns.get(match.group(1), match.group())
164 return ns.get(match.group(1), match.group())
159
165
160 return [ pat.sub(from_ns, arg) for arg in cmd ]
166 return [ pat.sub(from_ns, arg) for arg in cmd ]
161
167
162 def _launch_kernel(self, kernel_cmd, **kw):
168 def _launch_kernel(self, kernel_cmd, **kw):
163 """actually launch the kernel
169 """actually launch the kernel
164
170
165 override in a subclass to launch kernel subprocesses differently
171 override in a subclass to launch kernel subprocesses differently
166 """
172 """
167 return launch_kernel(kernel_cmd, **kw)
173 return launch_kernel(kernel_cmd, **kw)
168
174
169 # Control socket used for polite kernel shutdown
175 # Control socket used for polite kernel shutdown
170
176
171 def _connect_control_socket(self):
177 def _connect_control_socket(self):
172 if self._control_socket is None:
178 if self._control_socket is None:
173 self._control_socket = self.connect_control()
179 self._control_socket = self.connect_control()
174 self._control_socket.linger = 100
180 self._control_socket.linger = 100
175
181
176 def _close_control_socket(self):
182 def _close_control_socket(self):
177 if self._control_socket is None:
183 if self._control_socket is None:
178 return
184 return
179 self._control_socket.close()
185 self._control_socket.close()
180 self._control_socket = None
186 self._control_socket = None
181
187
182 def start_kernel(self, **kw):
188 def start_kernel(self, **kw):
183 """Starts a kernel on this host in a separate process.
189 """Starts a kernel on this host in a separate process.
184
190
185 If random ports (port=0) are being used, this method must be called
191 If random ports (port=0) are being used, this method must be called
186 before the channels are created.
192 before the channels are created.
187
193
188 Parameters
194 Parameters
189 ----------
195 ----------
190 **kw : optional
196 **kw : optional
191 keyword arguments that are passed down to build the kernel_cmd
197 keyword arguments that are passed down to build the kernel_cmd
192 and launching the kernel (e.g. Popen kwargs).
198 and launching the kernel (e.g. Popen kwargs).
193 """
199 """
194 if self.transport == 'tcp' and not is_local_ip(self.ip):
200 if self.transport == 'tcp' and not is_local_ip(self.ip):
195 raise RuntimeError("Can only launch a kernel on a local interface. "
201 raise RuntimeError("Can only launch a kernel on a local interface. "
196 "Make sure that the '*_address' attributes are "
202 "Make sure that the '*_address' attributes are "
197 "configured properly. "
203 "configured properly. "
198 "Currently valid addresses are: %s" % local_ips()
204 "Currently valid addresses are: %s" % local_ips()
199 )
205 )
200
206
201 # write connection file / get default ports
207 # write connection file / get default ports
202 self.write_connection_file()
208 self.write_connection_file()
203
209
204 # save kwargs for use in restart
210 # save kwargs for use in restart
205 self._launch_args = kw.copy()
211 self._launch_args = kw.copy()
206 # build the Popen cmd
212 # build the Popen cmd
207 kernel_cmd = self.format_kernel_cmd(**kw)
213 kernel_cmd = self.format_kernel_cmd(**kw)
208 # launch the kernel subprocess
214 # launch the kernel subprocess
209 self.kernel = self._launch_kernel(kernel_cmd,
215 self.kernel = self._launch_kernel(kernel_cmd,
210 ipython_kernel=self.ipython_kernel,
216 ipython_kernel=self.ipython_kernel,
211 **kw)
217 **kw)
212 self.start_restarter()
218 self.start_restarter()
213 self._connect_control_socket()
219 self._connect_control_socket()
214
220
215 def _send_shutdown_request(self, restart=False):
221 def _send_shutdown_request(self, restart=False):
216 """TODO: send a shutdown request via control channel"""
222 """TODO: send a shutdown request via control channel"""
217 content = dict(restart=restart)
223 content = dict(restart=restart)
218 msg = self.session.msg("shutdown_request", content=content)
224 msg = self.session.msg("shutdown_request", content=content)
219 self.session.send(self._control_socket, msg)
225 self.session.send(self._control_socket, msg)
220
226
221 def shutdown_kernel(self, now=False, restart=False):
227 def shutdown_kernel(self, now=False, restart=False):
222 """Attempts to the stop the kernel process cleanly.
228 """Attempts to the stop the kernel process cleanly.
223
229
224 This attempts to shutdown the kernels cleanly by:
230 This attempts to shutdown the kernels cleanly by:
225
231
226 1. Sending it a shutdown message over the shell channel.
232 1. Sending it a shutdown message over the shell channel.
227 2. If that fails, the kernel is shutdown forcibly by sending it
233 2. If that fails, the kernel is shutdown forcibly by sending it
228 a signal.
234 a signal.
229
235
230 Parameters
236 Parameters
231 ----------
237 ----------
232 now : bool
238 now : bool
233 Should the kernel be forcible killed *now*. This skips the
239 Should the kernel be forcible killed *now*. This skips the
234 first, nice shutdown attempt.
240 first, nice shutdown attempt.
235 restart: bool
241 restart: bool
236 Will this kernel be restarted after it is shutdown. When this
242 Will this kernel be restarted after it is shutdown. When this
237 is True, connection files will not be cleaned up.
243 is True, connection files will not be cleaned up.
238 """
244 """
239 # Stop monitoring for restarting while we shutdown.
245 # Stop monitoring for restarting while we shutdown.
240 self.stop_restarter()
246 self.stop_restarter()
241
247
242 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
248 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
243 if sys.platform == 'win32':
249 if sys.platform == 'win32':
244 self._kill_kernel()
250 self._kill_kernel()
245 return
251 return
246
252
247 if now:
253 if now:
248 if self.has_kernel:
254 if self.has_kernel:
249 self._kill_kernel()
255 self._kill_kernel()
250 else:
256 else:
251 # Don't send any additional kernel kill messages immediately, to give
257 # Don't send any additional kernel kill messages immediately, to give
252 # the kernel a chance to properly execute shutdown actions. Wait for at
258 # the kernel a chance to properly execute shutdown actions. Wait for at
253 # most 1s, checking every 0.1s.
259 # most 1s, checking every 0.1s.
254 self._send_shutdown_request(restart=restart)
260 self._send_shutdown_request(restart=restart)
255 for i in range(10):
261 for i in range(10):
256 if self.is_alive():
262 if self.is_alive():
257 time.sleep(0.1)
263 time.sleep(0.1)
258 else:
264 else:
259 break
265 break
260 else:
266 else:
261 # OK, we've waited long enough.
267 # OK, we've waited long enough.
262 if self.has_kernel:
268 if self.has_kernel:
263 self._kill_kernel()
269 self._kill_kernel()
264
270
265 if not restart:
271 if not restart:
266 self.cleanup_connection_file()
272 self.cleanup_connection_file()
267 self.cleanup_ipc_files()
273 self.cleanup_ipc_files()
268 else:
274 else:
269 self.cleanup_ipc_files()
275 self.cleanup_ipc_files()
270
276
271 def restart_kernel(self, now=False, **kw):
277 def restart_kernel(self, now=False, **kw):
272 """Restarts a kernel with the arguments that were used to launch it.
278 """Restarts a kernel with the arguments that were used to launch it.
273
279
274 If the old kernel was launched with random ports, the same ports will be
280 If the old kernel was launched with random ports, the same ports will be
275 used for the new kernel. The same connection file is used again.
281 used for the new kernel. The same connection file is used again.
276
282
277 Parameters
283 Parameters
278 ----------
284 ----------
279 now : bool, optional
285 now : bool, optional
280 If True, the kernel is forcefully restarted *immediately*, without
286 If True, the kernel is forcefully restarted *immediately*, without
281 having a chance to do any cleanup action. Otherwise the kernel is
287 having a chance to do any cleanup action. Otherwise the kernel is
282 given 1s to clean up before a forceful restart is issued.
288 given 1s to clean up before a forceful restart is issued.
283
289
284 In all cases the kernel is restarted, the only difference is whether
290 In all cases the kernel is restarted, the only difference is whether
285 it is given a chance to perform a clean shutdown or not.
291 it is given a chance to perform a clean shutdown or not.
286
292
287 **kw : optional
293 **kw : optional
288 Any options specified here will overwrite those used to launch the
294 Any options specified here will overwrite those used to launch the
289 kernel.
295 kernel.
290 """
296 """
291 if self._launch_args is None:
297 if self._launch_args is None:
292 raise RuntimeError("Cannot restart the kernel. "
298 raise RuntimeError("Cannot restart the kernel. "
293 "No previous call to 'start_kernel'.")
299 "No previous call to 'start_kernel'.")
294 else:
300 else:
295 # Stop currently running kernel.
301 # Stop currently running kernel.
296 self.shutdown_kernel(now=now, restart=True)
302 self.shutdown_kernel(now=now, restart=True)
297
303
298 # Start new kernel.
304 # Start new kernel.
299 self._launch_args.update(kw)
305 self._launch_args.update(kw)
300 self.start_kernel(**self._launch_args)
306 self.start_kernel(**self._launch_args)
301
307
302 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
308 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
303 # unless there is some delay here.
309 # unless there is some delay here.
304 if sys.platform == 'win32':
310 if sys.platform == 'win32':
305 time.sleep(0.2)
311 time.sleep(0.2)
306
312
307 @property
313 @property
308 def has_kernel(self):
314 def has_kernel(self):
309 """Has a kernel been started that we are managing."""
315 """Has a kernel been started that we are managing."""
310 return self.kernel is not None
316 return self.kernel is not None
311
317
312 def _kill_kernel(self):
318 def _kill_kernel(self):
313 """Kill the running kernel.
319 """Kill the running kernel.
314
320
315 This is a private method, callers should use shutdown_kernel(now=True).
321 This is a private method, callers should use shutdown_kernel(now=True).
316 """
322 """
317 if self.has_kernel:
323 if self.has_kernel:
318
324
319 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
325 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
320 # TerminateProcess() on Win32).
326 # TerminateProcess() on Win32).
321 try:
327 try:
322 self.kernel.kill()
328 self.kernel.kill()
323 except OSError as e:
329 except OSError as e:
324 # In Windows, we will get an Access Denied error if the process
330 # In Windows, we will get an Access Denied error if the process
325 # has already terminated. Ignore it.
331 # has already terminated. Ignore it.
326 if sys.platform == 'win32':
332 if sys.platform == 'win32':
327 if e.winerror != 5:
333 if e.winerror != 5:
328 raise
334 raise
329 # On Unix, we may get an ESRCH error if the process has already
335 # On Unix, we may get an ESRCH error if the process has already
330 # terminated. Ignore it.
336 # terminated. Ignore it.
331 else:
337 else:
332 from errno import ESRCH
338 from errno import ESRCH
333 if e.errno != ESRCH:
339 if e.errno != ESRCH:
334 raise
340 raise
335
341
336 # Block until the kernel terminates.
342 # Block until the kernel terminates.
337 self.kernel.wait()
343 self.kernel.wait()
338 self.kernel = None
344 self.kernel = None
339 else:
345 else:
340 raise RuntimeError("Cannot kill kernel. No kernel is running!")
346 raise RuntimeError("Cannot kill kernel. No kernel is running!")
341
347
342 def interrupt_kernel(self):
348 def interrupt_kernel(self):
343 """Interrupts the kernel by sending it a signal.
349 """Interrupts the kernel by sending it a signal.
344
350
345 Unlike ``signal_kernel``, this operation is well supported on all
351 Unlike ``signal_kernel``, this operation is well supported on all
346 platforms.
352 platforms.
347 """
353 """
348 if self.has_kernel:
354 if self.has_kernel:
349 if sys.platform == 'win32':
355 if sys.platform == 'win32':
350 from .zmq.parentpoller import ParentPollerWindows as Poller
356 from .zmq.parentpoller import ParentPollerWindows as Poller
351 Poller.send_interrupt(self.kernel.win32_interrupt_event)
357 Poller.send_interrupt(self.kernel.win32_interrupt_event)
352 else:
358 else:
353 self.kernel.send_signal(signal.SIGINT)
359 self.kernel.send_signal(signal.SIGINT)
354 else:
360 else:
355 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
361 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
356
362
357 def signal_kernel(self, signum):
363 def signal_kernel(self, signum):
358 """Sends a signal to the kernel.
364 """Sends a signal to the kernel.
359
365
360 Note that since only SIGTERM is supported on Windows, this function is
366 Note that since only SIGTERM is supported on Windows, this function is
361 only useful on Unix systems.
367 only useful on Unix systems.
362 """
368 """
363 if self.has_kernel:
369 if self.has_kernel:
364 self.kernel.send_signal(signum)
370 self.kernel.send_signal(signum)
365 else:
371 else:
366 raise RuntimeError("Cannot signal kernel. No kernel is running!")
372 raise RuntimeError("Cannot signal kernel. No kernel is running!")
367
373
368 def is_alive(self):
374 def is_alive(self):
369 """Is the kernel process still running?"""
375 """Is the kernel process still running?"""
370 if self.has_kernel:
376 if self.has_kernel:
371 if self.kernel.poll() is None:
377 if self.kernel.poll() is None:
372 return True
378 return True
373 else:
379 else:
374 return False
380 return False
375 else:
381 else:
376 # we don't have a kernel
382 # we don't have a kernel
377 return False
383 return False
378
384
379
385
380 #-----------------------------------------------------------------------------
386 #-----------------------------------------------------------------------------
381 # ABC Registration
387 # ABC Registration
382 #-----------------------------------------------------------------------------
388 #-----------------------------------------------------------------------------
383
389
384 KernelManagerABC.register(KernelManager)
390 KernelManagerABC.register(KernelManager)
385
391
General Comments 0
You need to be logged in to leave comments. Login now