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