##// END OF EJS Templates
Rename wait_shutdown to finish_shutdown
Thomas Kluyver -
Show More
@@ -1,428 +1,428 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 # Standard library imports
8 # Standard library imports
9 import os
9 import os
10 import re
10 import re
11 import signal
11 import signal
12 import sys
12 import sys
13 import time
13 import time
14 import warnings
14 import warnings
15
15
16 import zmq
16 import zmq
17
17
18 # Local imports
18 # Local imports
19 from IPython.config.configurable import LoggingConfigurable
19 from IPython.config.configurable import LoggingConfigurable
20 from IPython.utils.importstring import import_item
20 from IPython.utils.importstring import import_item
21 from IPython.utils.localinterfaces import is_local_ip, local_ips
21 from IPython.utils.localinterfaces import is_local_ip, local_ips
22 from IPython.utils.path import get_ipython_dir
22 from IPython.utils.path import get_ipython_dir
23 from IPython.utils.traitlets import (
23 from IPython.utils.traitlets import (
24 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
24 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
25 )
25 )
26 from IPython.kernel import (
26 from IPython.kernel import (
27 make_ipkernel_cmd,
27 make_ipkernel_cmd,
28 launch_kernel,
28 launch_kernel,
29 kernelspec,
29 kernelspec,
30 )
30 )
31 from .connect import ConnectionFileMixin
31 from .connect import ConnectionFileMixin
32 from .zmq.session import Session
32 from .zmq.session import Session
33 from .managerabc import (
33 from .managerabc import (
34 KernelManagerABC
34 KernelManagerABC
35 )
35 )
36
36
37
37
38 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
38 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
39 """Manages a single kernel in a subprocess on this host.
39 """Manages a single kernel in a subprocess on this host.
40
40
41 This version starts kernels with Popen.
41 This version starts kernels with Popen.
42 """
42 """
43
43
44 # The PyZMQ Context to use for communication with the kernel.
44 # The PyZMQ Context to use for communication with the kernel.
45 context = Instance(zmq.Context)
45 context = Instance(zmq.Context)
46 def _context_default(self):
46 def _context_default(self):
47 return zmq.Context.instance()
47 return zmq.Context.instance()
48
48
49 # The Session to use for communication with the kernel.
49 # The Session to use for communication with the kernel.
50 session = Instance(Session)
50 session = Instance(Session)
51 def _session_default(self):
51 def _session_default(self):
52 return Session(parent=self)
52 return Session(parent=self)
53
53
54 # the class to create with our `client` method
54 # the class to create with our `client` method
55 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
55 client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient')
56 client_factory = Type()
56 client_factory = Type()
57 def _client_class_changed(self, name, old, new):
57 def _client_class_changed(self, name, old, new):
58 self.client_factory = import_item(str(new))
58 self.client_factory = import_item(str(new))
59
59
60 # The kernel process with which the KernelManager is communicating.
60 # The kernel process with which the KernelManager is communicating.
61 # generally a Popen instance
61 # generally a Popen instance
62 kernel = Any()
62 kernel = Any()
63
63
64 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
64 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
65
65
66 def _kernel_spec_manager_default(self):
66 def _kernel_spec_manager_default(self):
67 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
67 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
68
68
69 kernel_name = Unicode('python')
69 kernel_name = Unicode('python')
70
70
71 kernel_spec = Instance(kernelspec.KernelSpec)
71 kernel_spec = Instance(kernelspec.KernelSpec)
72
72
73 def _kernel_spec_default(self):
73 def _kernel_spec_default(self):
74 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
74 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
75
75
76 def _kernel_name_changed(self, name, old, new):
76 def _kernel_name_changed(self, name, old, new):
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
79
79
80 kernel_cmd = List(Unicode, config=True,
80 kernel_cmd = List(Unicode, config=True,
81 help="""DEPRECATED: Use kernel_name instead.
81 help="""DEPRECATED: Use kernel_name instead.
82
82
83 The Popen Command to launch the kernel.
83 The Popen Command to launch the kernel.
84 Override this if you have a custom kernel.
84 Override this if you have a custom kernel.
85 If kernel_cmd is specified in a configuration file,
85 If kernel_cmd is specified in a configuration file,
86 IPython does not pass any arguments to the kernel,
86 IPython does not pass any arguments to the kernel,
87 because it cannot make any assumptions about the
87 because it cannot make any assumptions about the
88 arguments that the kernel understands. In particular,
88 arguments that the kernel understands. In particular,
89 this means that the kernel does not receive the
89 this means that the kernel does not receive the
90 option --debug if it given on the IPython command line.
90 option --debug if it given on the IPython command line.
91 """
91 """
92 )
92 )
93
93
94 def _kernel_cmd_changed(self, name, old, new):
94 def _kernel_cmd_changed(self, name, old, new):
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
96 "start different kernels.")
96 "start different kernels.")
97 self.ipython_kernel = False
97 self.ipython_kernel = False
98
98
99 ipython_kernel = Bool(True)
99 ipython_kernel = Bool(True)
100
100
101 ipython_dir = Unicode()
101 ipython_dir = Unicode()
102 def _ipython_dir_default(self):
102 def _ipython_dir_default(self):
103 return get_ipython_dir()
103 return get_ipython_dir()
104
104
105 # Protected traits
105 # Protected traits
106 _launch_args = Any()
106 _launch_args = Any()
107 _control_socket = Any()
107 _control_socket = Any()
108
108
109 _restarter = Any()
109 _restarter = Any()
110
110
111 autorestart = Bool(False, config=True,
111 autorestart = Bool(False, config=True,
112 help="""Should we autorestart the kernel if it dies."""
112 help="""Should we autorestart the kernel if it dies."""
113 )
113 )
114
114
115 def __del__(self):
115 def __del__(self):
116 self._close_control_socket()
116 self._close_control_socket()
117 self.cleanup_connection_file()
117 self.cleanup_connection_file()
118
118
119 #--------------------------------------------------------------------------
119 #--------------------------------------------------------------------------
120 # Kernel restarter
120 # Kernel restarter
121 #--------------------------------------------------------------------------
121 #--------------------------------------------------------------------------
122
122
123 def start_restarter(self):
123 def start_restarter(self):
124 pass
124 pass
125
125
126 def stop_restarter(self):
126 def stop_restarter(self):
127 pass
127 pass
128
128
129 def add_restart_callback(self, callback, event='restart'):
129 def add_restart_callback(self, callback, event='restart'):
130 """register a callback to be called when a kernel is restarted"""
130 """register a callback to be called when a kernel is restarted"""
131 if self._restarter is None:
131 if self._restarter is None:
132 return
132 return
133 self._restarter.add_callback(callback, event)
133 self._restarter.add_callback(callback, event)
134
134
135 def remove_restart_callback(self, callback, event='restart'):
135 def remove_restart_callback(self, callback, event='restart'):
136 """unregister a callback to be called when a kernel is restarted"""
136 """unregister a callback to be called when a kernel is restarted"""
137 if self._restarter is None:
137 if self._restarter is None:
138 return
138 return
139 self._restarter.remove_callback(callback, event)
139 self._restarter.remove_callback(callback, event)
140
140
141 #--------------------------------------------------------------------------
141 #--------------------------------------------------------------------------
142 # create a Client connected to our Kernel
142 # create a Client connected to our Kernel
143 #--------------------------------------------------------------------------
143 #--------------------------------------------------------------------------
144
144
145 def client(self, **kwargs):
145 def client(self, **kwargs):
146 """Create a client configured to connect to our kernel"""
146 """Create a client configured to connect to our kernel"""
147 if self.client_factory is None:
147 if self.client_factory is None:
148 self.client_factory = import_item(self.client_class)
148 self.client_factory = import_item(self.client_class)
149
149
150 kw = {}
150 kw = {}
151 kw.update(self.get_connection_info())
151 kw.update(self.get_connection_info())
152 kw.update(dict(
152 kw.update(dict(
153 connection_file=self.connection_file,
153 connection_file=self.connection_file,
154 session=self.session,
154 session=self.session,
155 parent=self,
155 parent=self,
156 ))
156 ))
157
157
158 # add kwargs last, for manual overrides
158 # add kwargs last, for manual overrides
159 kw.update(kwargs)
159 kw.update(kwargs)
160 return self.client_factory(**kw)
160 return self.client_factory(**kw)
161
161
162 #--------------------------------------------------------------------------
162 #--------------------------------------------------------------------------
163 # Kernel management
163 # Kernel management
164 #--------------------------------------------------------------------------
164 #--------------------------------------------------------------------------
165
165
166 def format_kernel_cmd(self, **kw):
166 def format_kernel_cmd(self, **kw):
167 """replace templated args (e.g. {connection_file})"""
167 """replace templated args (e.g. {connection_file})"""
168 if self.kernel_cmd:
168 if self.kernel_cmd:
169 cmd = self.kernel_cmd
169 cmd = self.kernel_cmd
170 elif self.kernel_name == 'python':
170 elif self.kernel_name == 'python':
171 # The native kernel gets special handling
171 # The native kernel gets special handling
172 cmd = make_ipkernel_cmd(
172 cmd = make_ipkernel_cmd(
173 'from IPython.kernel.zmq.kernelapp import main; main()',
173 'from IPython.kernel.zmq.kernelapp import main; main()',
174 **kw
174 **kw
175 )
175 )
176 else:
176 else:
177 cmd = self.kernel_spec.argv
177 cmd = self.kernel_spec.argv
178
178
179 ns = dict(connection_file=self.connection_file)
179 ns = dict(connection_file=self.connection_file)
180 ns.update(self._launch_args)
180 ns.update(self._launch_args)
181
181
182 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
182 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
183 def from_ns(match):
183 def from_ns(match):
184 """Get the key out of ns if it's there, otherwise no change."""
184 """Get the key out of ns if it's there, otherwise no change."""
185 return ns.get(match.group(1), match.group())
185 return ns.get(match.group(1), match.group())
186
186
187 return [ pat.sub(from_ns, arg) for arg in cmd ]
187 return [ pat.sub(from_ns, arg) for arg in cmd ]
188
188
189 def _launch_kernel(self, kernel_cmd, **kw):
189 def _launch_kernel(self, kernel_cmd, **kw):
190 """actually launch the kernel
190 """actually launch the kernel
191
191
192 override in a subclass to launch kernel subprocesses differently
192 override in a subclass to launch kernel subprocesses differently
193 """
193 """
194 return launch_kernel(kernel_cmd, **kw)
194 return launch_kernel(kernel_cmd, **kw)
195
195
196 # Control socket used for polite kernel shutdown
196 # Control socket used for polite kernel shutdown
197
197
198 def _connect_control_socket(self):
198 def _connect_control_socket(self):
199 if self._control_socket is None:
199 if self._control_socket is None:
200 self._control_socket = self.connect_control()
200 self._control_socket = self.connect_control()
201 self._control_socket.linger = 100
201 self._control_socket.linger = 100
202
202
203 def _close_control_socket(self):
203 def _close_control_socket(self):
204 if self._control_socket is None:
204 if self._control_socket is None:
205 return
205 return
206 self._control_socket.close()
206 self._control_socket.close()
207 self._control_socket = None
207 self._control_socket = None
208
208
209 def start_kernel(self, **kw):
209 def start_kernel(self, **kw):
210 """Starts a kernel on this host in a separate process.
210 """Starts a kernel on this host in a separate process.
211
211
212 If random ports (port=0) are being used, this method must be called
212 If random ports (port=0) are being used, this method must be called
213 before the channels are created.
213 before the channels are created.
214
214
215 Parameters
215 Parameters
216 ----------
216 ----------
217 **kw : optional
217 **kw : optional
218 keyword arguments that are passed down to build the kernel_cmd
218 keyword arguments that are passed down to build the kernel_cmd
219 and launching the kernel (e.g. Popen kwargs).
219 and launching the kernel (e.g. Popen kwargs).
220 """
220 """
221 if self.transport == 'tcp' and not is_local_ip(self.ip):
221 if self.transport == 'tcp' and not is_local_ip(self.ip):
222 raise RuntimeError("Can only launch a kernel on a local interface. "
222 raise RuntimeError("Can only launch a kernel on a local interface. "
223 "Make sure that the '*_address' attributes are "
223 "Make sure that the '*_address' attributes are "
224 "configured properly. "
224 "configured properly. "
225 "Currently valid addresses are: %s" % local_ips()
225 "Currently valid addresses are: %s" % local_ips()
226 )
226 )
227
227
228 # write connection file / get default ports
228 # write connection file / get default ports
229 self.write_connection_file()
229 self.write_connection_file()
230
230
231 # save kwargs for use in restart
231 # save kwargs for use in restart
232 self._launch_args = kw.copy()
232 self._launch_args = kw.copy()
233 # build the Popen cmd
233 # build the Popen cmd
234 kernel_cmd = self.format_kernel_cmd(**kw)
234 kernel_cmd = self.format_kernel_cmd(**kw)
235 if self.kernel_cmd:
235 if self.kernel_cmd:
236 # If kernel_cmd has been set manually, don't refer to a kernel spec
236 # If kernel_cmd has been set manually, don't refer to a kernel spec
237 env = os.environ
237 env = os.environ
238 else:
238 else:
239 # Environment variables from kernel spec are added to os.environ
239 # Environment variables from kernel spec are added to os.environ
240 env = os.environ.copy()
240 env = os.environ.copy()
241 env.update(self.kernel_spec.env or {})
241 env.update(self.kernel_spec.env or {})
242 # launch the kernel subprocess
242 # launch the kernel subprocess
243 self.kernel = self._launch_kernel(kernel_cmd, env=env,
243 self.kernel = self._launch_kernel(kernel_cmd, env=env,
244 ipython_kernel=self.ipython_kernel,
244 ipython_kernel=self.ipython_kernel,
245 **kw)
245 **kw)
246 self.start_restarter()
246 self.start_restarter()
247 self._connect_control_socket()
247 self._connect_control_socket()
248
248
249 def request_shutdown(self, restart=False):
249 def request_shutdown(self, restart=False):
250 """Send a shutdown request via control channel
250 """Send a shutdown request via control channel
251
251
252 On Windows, this just kills kernels instead, because the shutdown
252 On Windows, this just kills kernels instead, because the shutdown
253 messages don't work.
253 messages don't work.
254 """
254 """
255 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
255 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
256 if sys.platform == 'win32' and self.has_kernel:
256 if sys.platform == 'win32' and self.has_kernel:
257 return self._kill_kernel()
257 return self._kill_kernel()
258 content = dict(restart=restart)
258 content = dict(restart=restart)
259 msg = self.session.msg("shutdown_request", content=content)
259 msg = self.session.msg("shutdown_request", content=content)
260 self.session.send(self._control_socket, msg)
260 self.session.send(self._control_socket, msg)
261
261
262 def wait_shutdown(self, totaltime=1, interval=0.1):
262 def finish_shutdown(self, waittime=1, pollinterval=0.1):
263 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
263 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
264
264
265 This does not send shutdown requests - use :meth:`request_shutdown`
265 This does not send shutdown requests - use :meth:`request_shutdown`
266 first.
266 first.
267 """
267 """
268 for i in range(int(totaltime/interval)):
268 for i in range(int(waittime/pollinterval)):
269 if self.is_alive():
269 if self.is_alive():
270 time.sleep(interval)
270 time.sleep(pollinterval)
271 else:
271 else:
272 break
272 break
273 else:
273 else:
274 # OK, we've waited long enough.
274 # OK, we've waited long enough.
275 if self.has_kernel:
275 if self.has_kernel:
276 self._kill_kernel()
276 self._kill_kernel()
277
277
278 def cleanup(self, connection_file=True):
278 def cleanup(self, connection_file=True):
279 """Clean up resources when the kernel is shut down"""
279 """Clean up resources when the kernel is shut down"""
280 if connection_file:
280 if connection_file:
281 self.cleanup_connection_file()
281 self.cleanup_connection_file()
282
282
283 self.cleanup_ipc_files()
283 self.cleanup_ipc_files()
284 self._close_control_socket()
284 self._close_control_socket()
285
285
286 def shutdown_kernel(self, now=False, restart=False):
286 def shutdown_kernel(self, now=False, restart=False):
287 """Attempts to the stop the kernel process cleanly.
287 """Attempts to the stop the kernel process cleanly.
288
288
289 This attempts to shutdown the kernels cleanly by:
289 This attempts to shutdown the kernels cleanly by:
290
290
291 1. Sending it a shutdown message over the shell channel.
291 1. Sending it a shutdown message over the shell channel.
292 2. If that fails, the kernel is shutdown forcibly by sending it
292 2. If that fails, the kernel is shutdown forcibly by sending it
293 a signal.
293 a signal.
294
294
295 Parameters
295 Parameters
296 ----------
296 ----------
297 now : bool
297 now : bool
298 Should the kernel be forcible killed *now*. This skips the
298 Should the kernel be forcible killed *now*. This skips the
299 first, nice shutdown attempt.
299 first, nice shutdown attempt.
300 restart: bool
300 restart: bool
301 Will this kernel be restarted after it is shutdown. When this
301 Will this kernel be restarted after it is shutdown. When this
302 is True, connection files will not be cleaned up.
302 is True, connection files will not be cleaned up.
303 """
303 """
304 # Stop monitoring for restarting while we shutdown.
304 # Stop monitoring for restarting while we shutdown.
305 self.stop_restarter()
305 self.stop_restarter()
306
306
307 if now:
307 if now:
308 self._kill_kernel()
308 self._kill_kernel()
309 else:
309 else:
310 self.request_shutdown(restart=restart)
310 self.request_shutdown(restart=restart)
311 # Don't send any additional kernel kill messages immediately, to give
311 # Don't send any additional kernel kill messages immediately, to give
312 # the kernel a chance to properly execute shutdown actions. Wait for at
312 # the kernel a chance to properly execute shutdown actions. Wait for at
313 # most 1s, checking every 0.1s.
313 # most 1s, checking every 0.1s.
314 self.wait_shutdown()
314 self.finish_shutdown()
315
315
316 self.cleanup(connection_file=not restart)
316 self.cleanup(connection_file=not restart)
317
317
318 def restart_kernel(self, now=False, **kw):
318 def restart_kernel(self, now=False, **kw):
319 """Restarts a kernel with the arguments that were used to launch it.
319 """Restarts a kernel with the arguments that were used to launch it.
320
320
321 If the old kernel was launched with random ports, the same ports will be
321 If the old kernel was launched with random ports, the same ports will be
322 used for the new kernel. The same connection file is used again.
322 used for the new kernel. The same connection file is used again.
323
323
324 Parameters
324 Parameters
325 ----------
325 ----------
326 now : bool, optional
326 now : bool, optional
327 If True, the kernel is forcefully restarted *immediately*, without
327 If True, the kernel is forcefully restarted *immediately*, without
328 having a chance to do any cleanup action. Otherwise the kernel is
328 having a chance to do any cleanup action. Otherwise the kernel is
329 given 1s to clean up before a forceful restart is issued.
329 given 1s to clean up before a forceful restart is issued.
330
330
331 In all cases the kernel is restarted, the only difference is whether
331 In all cases the kernel is restarted, the only difference is whether
332 it is given a chance to perform a clean shutdown or not.
332 it is given a chance to perform a clean shutdown or not.
333
333
334 **kw : optional
334 **kw : optional
335 Any options specified here will overwrite those used to launch the
335 Any options specified here will overwrite those used to launch the
336 kernel.
336 kernel.
337 """
337 """
338 if self._launch_args is None:
338 if self._launch_args is None:
339 raise RuntimeError("Cannot restart the kernel. "
339 raise RuntimeError("Cannot restart the kernel. "
340 "No previous call to 'start_kernel'.")
340 "No previous call to 'start_kernel'.")
341 else:
341 else:
342 # Stop currently running kernel.
342 # Stop currently running kernel.
343 self.shutdown_kernel(now=now, restart=True)
343 self.shutdown_kernel(now=now, restart=True)
344
344
345 # Start new kernel.
345 # Start new kernel.
346 self._launch_args.update(kw)
346 self._launch_args.update(kw)
347 self.start_kernel(**self._launch_args)
347 self.start_kernel(**self._launch_args)
348
348
349 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
349 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
350 # unless there is some delay here.
350 # unless there is some delay here.
351 if sys.platform == 'win32':
351 if sys.platform == 'win32':
352 time.sleep(0.2)
352 time.sleep(0.2)
353
353
354 @property
354 @property
355 def has_kernel(self):
355 def has_kernel(self):
356 """Has a kernel been started that we are managing."""
356 """Has a kernel been started that we are managing."""
357 return self.kernel is not None
357 return self.kernel is not None
358
358
359 def _kill_kernel(self):
359 def _kill_kernel(self):
360 """Kill the running kernel.
360 """Kill the running kernel.
361
361
362 This is a private method, callers should use shutdown_kernel(now=True).
362 This is a private method, callers should use shutdown_kernel(now=True).
363 """
363 """
364 if self.has_kernel:
364 if self.has_kernel:
365
365
366 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
366 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
367 # TerminateProcess() on Win32).
367 # TerminateProcess() on Win32).
368 try:
368 try:
369 self.kernel.kill()
369 self.kernel.kill()
370 except OSError as e:
370 except OSError as e:
371 # In Windows, we will get an Access Denied error if the process
371 # In Windows, we will get an Access Denied error if the process
372 # has already terminated. Ignore it.
372 # has already terminated. Ignore it.
373 if sys.platform == 'win32':
373 if sys.platform == 'win32':
374 if e.winerror != 5:
374 if e.winerror != 5:
375 raise
375 raise
376 # On Unix, we may get an ESRCH error if the process has already
376 # On Unix, we may get an ESRCH error if the process has already
377 # terminated. Ignore it.
377 # terminated. Ignore it.
378 else:
378 else:
379 from errno import ESRCH
379 from errno import ESRCH
380 if e.errno != ESRCH:
380 if e.errno != ESRCH:
381 raise
381 raise
382
382
383 # Block until the kernel terminates.
383 # Block until the kernel terminates.
384 self.kernel.wait()
384 self.kernel.wait()
385 self.kernel = None
385 self.kernel = None
386 else:
386 else:
387 raise RuntimeError("Cannot kill kernel. No kernel is running!")
387 raise RuntimeError("Cannot kill kernel. No kernel is running!")
388
388
389 def interrupt_kernel(self):
389 def interrupt_kernel(self):
390 """Interrupts the kernel by sending it a signal.
390 """Interrupts the kernel by sending it a signal.
391
391
392 Unlike ``signal_kernel``, this operation is well supported on all
392 Unlike ``signal_kernel``, this operation is well supported on all
393 platforms.
393 platforms.
394 """
394 """
395 if self.has_kernel:
395 if self.has_kernel:
396 if sys.platform == 'win32':
396 if sys.platform == 'win32':
397 from .zmq.parentpoller import ParentPollerWindows as Poller
397 from .zmq.parentpoller import ParentPollerWindows as Poller
398 Poller.send_interrupt(self.kernel.win32_interrupt_event)
398 Poller.send_interrupt(self.kernel.win32_interrupt_event)
399 else:
399 else:
400 self.kernel.send_signal(signal.SIGINT)
400 self.kernel.send_signal(signal.SIGINT)
401 else:
401 else:
402 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
402 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
403
403
404 def signal_kernel(self, signum):
404 def signal_kernel(self, signum):
405 """Sends a signal to the kernel.
405 """Sends a signal to the kernel.
406
406
407 Note that since only SIGTERM is supported on Windows, this function is
407 Note that since only SIGTERM is supported on Windows, this function is
408 only useful on Unix systems.
408 only useful on Unix systems.
409 """
409 """
410 if self.has_kernel:
410 if self.has_kernel:
411 self.kernel.send_signal(signum)
411 self.kernel.send_signal(signum)
412 else:
412 else:
413 raise RuntimeError("Cannot signal kernel. No kernel is running!")
413 raise RuntimeError("Cannot signal kernel. No kernel is running!")
414
414
415 def is_alive(self):
415 def is_alive(self):
416 """Is the kernel process still running?"""
416 """Is the kernel process still running?"""
417 if self.has_kernel:
417 if self.has_kernel:
418 if self.kernel.poll() is None:
418 if self.kernel.poll() is None:
419 return True
419 return True
420 else:
420 else:
421 return False
421 return False
422 else:
422 else:
423 # we don't have a kernel
423 # we don't have a kernel
424 return False
424 return False
425
425
426
426
427 KernelManagerABC.register(KernelManager)
427 KernelManagerABC.register(KernelManager)
428
428
@@ -1,322 +1,322 b''
1 """A kernel manager for multiple kernels
1 """A kernel manager for multiple kernels
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from __future__ import absolute_import
19 from __future__ import absolute_import
20
20
21 import os
21 import os
22 import uuid
22 import uuid
23
23
24 import zmq
24 import zmq
25
25
26 from IPython.config.configurable import LoggingConfigurable
26 from IPython.config.configurable import LoggingConfigurable
27 from IPython.utils.importstring import import_item
27 from IPython.utils.importstring import import_item
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Instance, Dict, Unicode, Any, DottedObjectName
29 Instance, Dict, Unicode, Any, DottedObjectName
30 )
30 )
31 from IPython.utils.py3compat import unicode_type
31 from IPython.utils.py3compat import unicode_type
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Classes
34 # Classes
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class DuplicateKernelError(Exception):
37 class DuplicateKernelError(Exception):
38 pass
38 pass
39
39
40
40
41
41
42 def kernel_method(f):
42 def kernel_method(f):
43 """decorator for proxying MKM.method(kernel_id) to individual KMs by ID"""
43 """decorator for proxying MKM.method(kernel_id) to individual KMs by ID"""
44 def wrapped(self, kernel_id, *args, **kwargs):
44 def wrapped(self, kernel_id, *args, **kwargs):
45 # get the kernel
45 # get the kernel
46 km = self.get_kernel(kernel_id)
46 km = self.get_kernel(kernel_id)
47 method = getattr(km, f.__name__)
47 method = getattr(km, f.__name__)
48 # call the kernel's method
48 # call the kernel's method
49 r = method(*args, **kwargs)
49 r = method(*args, **kwargs)
50 # last thing, call anything defined in the actual class method
50 # last thing, call anything defined in the actual class method
51 # such as logging messages
51 # such as logging messages
52 f(self, kernel_id, *args, **kwargs)
52 f(self, kernel_id, *args, **kwargs)
53 # return the method result
53 # return the method result
54 return r
54 return r
55 return wrapped
55 return wrapped
56
56
57
57
58 class MultiKernelManager(LoggingConfigurable):
58 class MultiKernelManager(LoggingConfigurable):
59 """A class for managing multiple kernels."""
59 """A class for managing multiple kernels."""
60
60
61 kernel_manager_class = DottedObjectName(
61 kernel_manager_class = DottedObjectName(
62 "IPython.kernel.ioloop.IOLoopKernelManager", config=True,
62 "IPython.kernel.ioloop.IOLoopKernelManager", config=True,
63 help="""The kernel manager class. This is configurable to allow
63 help="""The kernel manager class. This is configurable to allow
64 subclassing of the KernelManager for customized behavior.
64 subclassing of the KernelManager for customized behavior.
65 """
65 """
66 )
66 )
67 def _kernel_manager_class_changed(self, name, old, new):
67 def _kernel_manager_class_changed(self, name, old, new):
68 self.kernel_manager_factory = import_item(new)
68 self.kernel_manager_factory = import_item(new)
69
69
70 kernel_manager_factory = Any(help="this is kernel_manager_class after import")
70 kernel_manager_factory = Any(help="this is kernel_manager_class after import")
71 def _kernel_manager_factory_default(self):
71 def _kernel_manager_factory_default(self):
72 return import_item(self.kernel_manager_class)
72 return import_item(self.kernel_manager_class)
73
73
74 context = Instance('zmq.Context')
74 context = Instance('zmq.Context')
75 def _context_default(self):
75 def _context_default(self):
76 return zmq.Context.instance()
76 return zmq.Context.instance()
77
77
78 connection_dir = Unicode('')
78 connection_dir = Unicode('')
79
79
80 _kernels = Dict()
80 _kernels = Dict()
81
81
82 def list_kernel_ids(self):
82 def list_kernel_ids(self):
83 """Return a list of the kernel ids of the active kernels."""
83 """Return a list of the kernel ids of the active kernels."""
84 # Create a copy so we can iterate over kernels in operations
84 # Create a copy so we can iterate over kernels in operations
85 # that delete keys.
85 # that delete keys.
86 return list(self._kernels.keys())
86 return list(self._kernels.keys())
87
87
88 def __len__(self):
88 def __len__(self):
89 """Return the number of running kernels."""
89 """Return the number of running kernels."""
90 return len(self.list_kernel_ids())
90 return len(self.list_kernel_ids())
91
91
92 def __contains__(self, kernel_id):
92 def __contains__(self, kernel_id):
93 return kernel_id in self._kernels
93 return kernel_id in self._kernels
94
94
95 def start_kernel(self, **kwargs):
95 def start_kernel(self, **kwargs):
96 """Start a new kernel.
96 """Start a new kernel.
97
97
98 The caller can pick a kernel_id by passing one in as a keyword arg,
98 The caller can pick a kernel_id by passing one in as a keyword arg,
99 otherwise one will be picked using a uuid.
99 otherwise one will be picked using a uuid.
100
100
101 To silence the kernel's stdout/stderr, call this using::
101 To silence the kernel's stdout/stderr, call this using::
102
102
103 km.start_kernel(stdout=PIPE, stderr=PIPE)
103 km.start_kernel(stdout=PIPE, stderr=PIPE)
104
104
105 """
105 """
106 kernel_id = kwargs.pop('kernel_id', unicode_type(uuid.uuid4()))
106 kernel_id = kwargs.pop('kernel_id', unicode_type(uuid.uuid4()))
107 if kernel_id in self:
107 if kernel_id in self:
108 raise DuplicateKernelError('Kernel already exists: %s' % kernel_id)
108 raise DuplicateKernelError('Kernel already exists: %s' % kernel_id)
109 # kernel_manager_factory is the constructor for the KernelManager
109 # kernel_manager_factory is the constructor for the KernelManager
110 # subclass we are using. It can be configured as any Configurable,
110 # subclass we are using. It can be configured as any Configurable,
111 # including things like its transport and ip.
111 # including things like its transport and ip.
112 km = self.kernel_manager_factory(connection_file=os.path.join(
112 km = self.kernel_manager_factory(connection_file=os.path.join(
113 self.connection_dir, "kernel-%s.json" % kernel_id),
113 self.connection_dir, "kernel-%s.json" % kernel_id),
114 parent=self, autorestart=True, log=self.log
114 parent=self, autorestart=True, log=self.log
115 )
115 )
116 km.start_kernel(**kwargs)
116 km.start_kernel(**kwargs)
117 self._kernels[kernel_id] = km
117 self._kernels[kernel_id] = km
118 return kernel_id
118 return kernel_id
119
119
120 @kernel_method
120 @kernel_method
121 def shutdown_kernel(self, kernel_id, now=False, restart=False):
121 def shutdown_kernel(self, kernel_id, now=False, restart=False):
122 """Shutdown a kernel by its kernel uuid.
122 """Shutdown a kernel by its kernel uuid.
123
123
124 Parameters
124 Parameters
125 ==========
125 ==========
126 kernel_id : uuid
126 kernel_id : uuid
127 The id of the kernel to shutdown.
127 The id of the kernel to shutdown.
128 now : bool
128 now : bool
129 Should the kernel be shutdown forcibly using a signal.
129 Should the kernel be shutdown forcibly using a signal.
130 restart : bool
130 restart : bool
131 Will the kernel be restarted?
131 Will the kernel be restarted?
132 """
132 """
133 self.log.info("Kernel shutdown: %s" % kernel_id)
133 self.log.info("Kernel shutdown: %s" % kernel_id)
134 self.remove_kernel(kernel_id)
134 self.remove_kernel(kernel_id)
135
135
136 @kernel_method
136 @kernel_method
137 def request_shutdown(self, kernel_id, restart=False):
137 def request_shutdown(self, kernel_id, restart=False):
138 """Ask a kernel to shut down by its kernel uuid"""
138 """Ask a kernel to shut down by its kernel uuid"""
139
139
140 @kernel_method
140 @kernel_method
141 def wait_shutdown(self, kernel_id, totaltime=1, interval=0.1):
141 def finish_shutdown(self, kernel_id, waittime=1, pollinterval=0.1):
142 """Wait for a kernel to finish shutting down, and kill it if it doesn't
142 """Wait for a kernel to finish shutting down, and kill it if it doesn't
143 """
143 """
144 self.log.info("Kernel shutdown: %s" % kernel_id)
144 self.log.info("Kernel shutdown: %s" % kernel_id)
145
145
146 @kernel_method
146 @kernel_method
147 def cleanup(self, kernel_id, connection_file=True):
147 def cleanup(self, kernel_id, connection_file=True):
148 """Clean up a kernel's resources"""
148 """Clean up a kernel's resources"""
149
149
150 def remove_kernel(self, kernel_id):
150 def remove_kernel(self, kernel_id):
151 """remove a kernel from our mapping.
151 """remove a kernel from our mapping.
152
152
153 Mainly so that a kernel can be removed if it is already dead,
153 Mainly so that a kernel can be removed if it is already dead,
154 without having to call shutdown_kernel.
154 without having to call shutdown_kernel.
155
155
156 The kernel object is returned.
156 The kernel object is returned.
157 """
157 """
158 return self._kernels.pop(kernel_id)
158 return self._kernels.pop(kernel_id)
159
159
160 def shutdown_all(self, now=False):
160 def shutdown_all(self, now=False):
161 """Shutdown all kernels."""
161 """Shutdown all kernels."""
162 kids = self.list_kernel_ids()
162 kids = self.list_kernel_ids()
163 for kid in kids:
163 for kid in kids:
164 self.request_shutdown(kid)
164 self.request_shutdown(kid)
165 for kid in kids:
165 for kid in kids:
166 self.wait_shutdown(kid)
166 self.finish_shutdown(kid)
167 self.cleanup(kid)
167 self.cleanup(kid)
168
168
169 @kernel_method
169 @kernel_method
170 def interrupt_kernel(self, kernel_id):
170 def interrupt_kernel(self, kernel_id):
171 """Interrupt (SIGINT) the kernel by its uuid.
171 """Interrupt (SIGINT) the kernel by its uuid.
172
172
173 Parameters
173 Parameters
174 ==========
174 ==========
175 kernel_id : uuid
175 kernel_id : uuid
176 The id of the kernel to interrupt.
176 The id of the kernel to interrupt.
177 """
177 """
178 self.log.info("Kernel interrupted: %s" % kernel_id)
178 self.log.info("Kernel interrupted: %s" % kernel_id)
179
179
180 @kernel_method
180 @kernel_method
181 def signal_kernel(self, kernel_id, signum):
181 def signal_kernel(self, kernel_id, signum):
182 """Sends a signal to the kernel by its uuid.
182 """Sends a signal to the kernel by its uuid.
183
183
184 Note that since only SIGTERM is supported on Windows, this function
184 Note that since only SIGTERM is supported on Windows, this function
185 is only useful on Unix systems.
185 is only useful on Unix systems.
186
186
187 Parameters
187 Parameters
188 ==========
188 ==========
189 kernel_id : uuid
189 kernel_id : uuid
190 The id of the kernel to signal.
190 The id of the kernel to signal.
191 """
191 """
192 self.log.info("Signaled Kernel %s with %s" % (kernel_id, signum))
192 self.log.info("Signaled Kernel %s with %s" % (kernel_id, signum))
193
193
194 @kernel_method
194 @kernel_method
195 def restart_kernel(self, kernel_id, now=False):
195 def restart_kernel(self, kernel_id, now=False):
196 """Restart a kernel by its uuid, keeping the same ports.
196 """Restart a kernel by its uuid, keeping the same ports.
197
197
198 Parameters
198 Parameters
199 ==========
199 ==========
200 kernel_id : uuid
200 kernel_id : uuid
201 The id of the kernel to interrupt.
201 The id of the kernel to interrupt.
202 """
202 """
203 self.log.info("Kernel restarted: %s" % kernel_id)
203 self.log.info("Kernel restarted: %s" % kernel_id)
204
204
205 @kernel_method
205 @kernel_method
206 def is_alive(self, kernel_id):
206 def is_alive(self, kernel_id):
207 """Is the kernel alive.
207 """Is the kernel alive.
208
208
209 This calls KernelManager.is_alive() which calls Popen.poll on the
209 This calls KernelManager.is_alive() which calls Popen.poll on the
210 actual kernel subprocess.
210 actual kernel subprocess.
211
211
212 Parameters
212 Parameters
213 ==========
213 ==========
214 kernel_id : uuid
214 kernel_id : uuid
215 The id of the kernel.
215 The id of the kernel.
216 """
216 """
217
217
218 def _check_kernel_id(self, kernel_id):
218 def _check_kernel_id(self, kernel_id):
219 """check that a kernel id is valid"""
219 """check that a kernel id is valid"""
220 if kernel_id not in self:
220 if kernel_id not in self:
221 raise KeyError("Kernel with id not found: %s" % kernel_id)
221 raise KeyError("Kernel with id not found: %s" % kernel_id)
222
222
223 def get_kernel(self, kernel_id):
223 def get_kernel(self, kernel_id):
224 """Get the single KernelManager object for a kernel by its uuid.
224 """Get the single KernelManager object for a kernel by its uuid.
225
225
226 Parameters
226 Parameters
227 ==========
227 ==========
228 kernel_id : uuid
228 kernel_id : uuid
229 The id of the kernel.
229 The id of the kernel.
230 """
230 """
231 self._check_kernel_id(kernel_id)
231 self._check_kernel_id(kernel_id)
232 return self._kernels[kernel_id]
232 return self._kernels[kernel_id]
233
233
234 @kernel_method
234 @kernel_method
235 def add_restart_callback(self, kernel_id, callback, event='restart'):
235 def add_restart_callback(self, kernel_id, callback, event='restart'):
236 """add a callback for the KernelRestarter"""
236 """add a callback for the KernelRestarter"""
237
237
238 @kernel_method
238 @kernel_method
239 def remove_restart_callback(self, kernel_id, callback, event='restart'):
239 def remove_restart_callback(self, kernel_id, callback, event='restart'):
240 """remove a callback for the KernelRestarter"""
240 """remove a callback for the KernelRestarter"""
241
241
242 @kernel_method
242 @kernel_method
243 def get_connection_info(self, kernel_id):
243 def get_connection_info(self, kernel_id):
244 """Return a dictionary of connection data for a kernel.
244 """Return a dictionary of connection data for a kernel.
245
245
246 Parameters
246 Parameters
247 ==========
247 ==========
248 kernel_id : uuid
248 kernel_id : uuid
249 The id of the kernel.
249 The id of the kernel.
250
250
251 Returns
251 Returns
252 =======
252 =======
253 connection_dict : dict
253 connection_dict : dict
254 A dict of the information needed to connect to a kernel.
254 A dict of the information needed to connect to a kernel.
255 This includes the ip address and the integer port
255 This includes the ip address and the integer port
256 numbers of the different channels (stdin_port, iopub_port,
256 numbers of the different channels (stdin_port, iopub_port,
257 shell_port, hb_port).
257 shell_port, hb_port).
258 """
258 """
259
259
260 @kernel_method
260 @kernel_method
261 def connect_iopub(self, kernel_id, identity=None):
261 def connect_iopub(self, kernel_id, identity=None):
262 """Return a zmq Socket connected to the iopub channel.
262 """Return a zmq Socket connected to the iopub channel.
263
263
264 Parameters
264 Parameters
265 ==========
265 ==========
266 kernel_id : uuid
266 kernel_id : uuid
267 The id of the kernel
267 The id of the kernel
268 identity : bytes (optional)
268 identity : bytes (optional)
269 The zmq identity of the socket
269 The zmq identity of the socket
270
270
271 Returns
271 Returns
272 =======
272 =======
273 stream : zmq Socket or ZMQStream
273 stream : zmq Socket or ZMQStream
274 """
274 """
275
275
276 @kernel_method
276 @kernel_method
277 def connect_shell(self, kernel_id, identity=None):
277 def connect_shell(self, kernel_id, identity=None):
278 """Return a zmq Socket connected to the shell channel.
278 """Return a zmq Socket connected to the shell channel.
279
279
280 Parameters
280 Parameters
281 ==========
281 ==========
282 kernel_id : uuid
282 kernel_id : uuid
283 The id of the kernel
283 The id of the kernel
284 identity : bytes (optional)
284 identity : bytes (optional)
285 The zmq identity of the socket
285 The zmq identity of the socket
286
286
287 Returns
287 Returns
288 =======
288 =======
289 stream : zmq Socket or ZMQStream
289 stream : zmq Socket or ZMQStream
290 """
290 """
291
291
292 @kernel_method
292 @kernel_method
293 def connect_stdin(self, kernel_id, identity=None):
293 def connect_stdin(self, kernel_id, identity=None):
294 """Return a zmq Socket connected to the stdin channel.
294 """Return a zmq Socket connected to the stdin channel.
295
295
296 Parameters
296 Parameters
297 ==========
297 ==========
298 kernel_id : uuid
298 kernel_id : uuid
299 The id of the kernel
299 The id of the kernel
300 identity : bytes (optional)
300 identity : bytes (optional)
301 The zmq identity of the socket
301 The zmq identity of the socket
302
302
303 Returns
303 Returns
304 =======
304 =======
305 stream : zmq Socket or ZMQStream
305 stream : zmq Socket or ZMQStream
306 """
306 """
307
307
308 @kernel_method
308 @kernel_method
309 def connect_hb(self, kernel_id, identity=None):
309 def connect_hb(self, kernel_id, identity=None):
310 """Return a zmq Socket connected to the hb channel.
310 """Return a zmq Socket connected to the hb channel.
311
311
312 Parameters
312 Parameters
313 ==========
313 ==========
314 kernel_id : uuid
314 kernel_id : uuid
315 The id of the kernel
315 The id of the kernel
316 identity : bytes (optional)
316 identity : bytes (optional)
317 The zmq identity of the socket
317 The zmq identity of the socket
318
318
319 Returns
319 Returns
320 =======
320 =======
321 stream : zmq Socket or ZMQStream
321 stream : zmq Socket or ZMQStream
322 """
322 """
General Comments 0
You need to be logged in to leave comments. Login now