##// END OF EJS Templates
make BlockingKernelClient the default Client...
MinRK -
Show More
@@ -1,379 +1,379 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.importstring import import_item
26 from IPython.utils.importstring import import_item
27 from IPython.utils.localinterfaces import LOCAL_IPS
27 from IPython.utils.localinterfaces import 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(config=self.config)
59 return Session(config=self.config)
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.client.KernelClient')
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
74 """
74 """
75 )
75 )
76
76
77 def _kernel_cmd_changed(self, name, old, new):
77 def _kernel_cmd_changed(self, name, old, new):
78 self.ipython_kernel = False
78 self.ipython_kernel = False
79
79
80 ipython_kernel = Bool(True)
80 ipython_kernel = Bool(True)
81
81
82 # Protected traits
82 # Protected traits
83 _launch_args = Any()
83 _launch_args = Any()
84 _control_socket = Any()
84 _control_socket = Any()
85
85
86 _restarter = Any()
86 _restarter = Any()
87
87
88 autorestart = Bool(False, config=True,
88 autorestart = Bool(False, config=True,
89 help="""Should we autorestart the kernel if it dies."""
89 help="""Should we autorestart the kernel if it dies."""
90 )
90 )
91
91
92 def __del__(self):
92 def __del__(self):
93 self._close_control_socket()
93 self._close_control_socket()
94 self.cleanup_connection_file()
94 self.cleanup_connection_file()
95
95
96 #--------------------------------------------------------------------------
96 #--------------------------------------------------------------------------
97 # Kernel restarter
97 # Kernel restarter
98 #--------------------------------------------------------------------------
98 #--------------------------------------------------------------------------
99
99
100 def start_restarter(self):
100 def start_restarter(self):
101 pass
101 pass
102
102
103 def stop_restarter(self):
103 def stop_restarter(self):
104 pass
104 pass
105
105
106 def add_restart_callback(self, callback, event='restart'):
106 def add_restart_callback(self, callback, event='restart'):
107 """register a callback to be called when a kernel is restarted"""
107 """register a callback to be called when a kernel is restarted"""
108 if self._restarter is None:
108 if self._restarter is None:
109 return
109 return
110 self._restarter.add_callback(callback, event)
110 self._restarter.add_callback(callback, event)
111
111
112 def remove_restart_callback(self, callback, event='restart'):
112 def remove_restart_callback(self, callback, event='restart'):
113 """unregister a callback to be called when a kernel is restarted"""
113 """unregister a callback to be called when a kernel is restarted"""
114 if self._restarter is None:
114 if self._restarter is None:
115 return
115 return
116 self._restarter.remove_callback(callback, event)
116 self._restarter.remove_callback(callback, event)
117
117
118 #--------------------------------------------------------------------------
118 #--------------------------------------------------------------------------
119 # create a Client connected to our Kernel
119 # create a Client connected to our Kernel
120 #--------------------------------------------------------------------------
120 #--------------------------------------------------------------------------
121
121
122 def client(self, **kwargs):
122 def client(self, **kwargs):
123 """Create a client configured to connect to our kernel"""
123 """Create a client configured to connect to our kernel"""
124 if self.client_factory is None:
124 if self.client_factory is None:
125 self.client_factory = import_item(self.client_class)
125 self.client_factory = import_item(self.client_class)
126
126
127 kw = {}
127 kw = {}
128 kw.update(self.get_connection_info())
128 kw.update(self.get_connection_info())
129 kw.update(dict(
129 kw.update(dict(
130 connection_file=self.connection_file,
130 connection_file=self.connection_file,
131 session=self.session,
131 session=self.session,
132 config=self.config,
132 config=self.config,
133 ))
133 ))
134
134
135 # add kwargs last, for manual overrides
135 # add kwargs last, for manual overrides
136 kw.update(kwargs)
136 kw.update(kwargs)
137 return self.client_factory(**kw)
137 return self.client_factory(**kw)
138
138
139 #--------------------------------------------------------------------------
139 #--------------------------------------------------------------------------
140 # Kernel management
140 # Kernel management
141 #--------------------------------------------------------------------------
141 #--------------------------------------------------------------------------
142
142
143 def format_kernel_cmd(self, **kw):
143 def format_kernel_cmd(self, **kw):
144 """format templated args (e.g. {connection_file})"""
144 """format templated args (e.g. {connection_file})"""
145 if self.kernel_cmd:
145 if self.kernel_cmd:
146 cmd = self.kernel_cmd
146 cmd = self.kernel_cmd
147 else:
147 else:
148 cmd = make_ipkernel_cmd(
148 cmd = make_ipkernel_cmd(
149 'from IPython.kernel.zmq.kernelapp import main; main()',
149 'from IPython.kernel.zmq.kernelapp import main; main()',
150 **kw
150 **kw
151 )
151 )
152 ns = dict(connection_file=self.connection_file)
152 ns = dict(connection_file=self.connection_file)
153 ns.update(self._launch_args)
153 ns.update(self._launch_args)
154 return [ c.format(**ns) for c in cmd ]
154 return [ c.format(**ns) for c in cmd ]
155
155
156 def _launch_kernel(self, kernel_cmd, **kw):
156 def _launch_kernel(self, kernel_cmd, **kw):
157 """actually launch the kernel
157 """actually launch the kernel
158
158
159 override in a subclass to launch kernel subprocesses differently
159 override in a subclass to launch kernel subprocesses differently
160 """
160 """
161 return launch_kernel(kernel_cmd, **kw)
161 return launch_kernel(kernel_cmd, **kw)
162
162
163 # Control socket used for polite kernel shutdown
163 # Control socket used for polite kernel shutdown
164
164
165 def _connect_control_socket(self):
165 def _connect_control_socket(self):
166 if self._control_socket is None:
166 if self._control_socket is None:
167 self._control_socket = self.connect_control()
167 self._control_socket = self.connect_control()
168 self._control_socket.linger = 100
168 self._control_socket.linger = 100
169
169
170 def _close_control_socket(self):
170 def _close_control_socket(self):
171 if self._control_socket is None:
171 if self._control_socket is None:
172 return
172 return
173 self._control_socket.close()
173 self._control_socket.close()
174 self._control_socket = None
174 self._control_socket = None
175
175
176 def start_kernel(self, **kw):
176 def start_kernel(self, **kw):
177 """Starts a kernel on this host in a separate process.
177 """Starts a kernel on this host in a separate process.
178
178
179 If random ports (port=0) are being used, this method must be called
179 If random ports (port=0) are being used, this method must be called
180 before the channels are created.
180 before the channels are created.
181
181
182 Parameters:
182 Parameters:
183 -----------
183 -----------
184 **kw : optional
184 **kw : optional
185 keyword arguments that are passed down to build the kernel_cmd
185 keyword arguments that are passed down to build the kernel_cmd
186 and launching the kernel (e.g. Popen kwargs).
186 and launching the kernel (e.g. Popen kwargs).
187 """
187 """
188 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
188 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
189 raise RuntimeError("Can only launch a kernel on a local interface. "
189 raise RuntimeError("Can only launch a kernel on a local interface. "
190 "Make sure that the '*_address' attributes are "
190 "Make sure that the '*_address' attributes are "
191 "configured properly. "
191 "configured properly. "
192 "Currently valid addresses are: %s"%LOCAL_IPS
192 "Currently valid addresses are: %s"%LOCAL_IPS
193 )
193 )
194
194
195 # write connection file / get default ports
195 # write connection file / get default ports
196 self.write_connection_file()
196 self.write_connection_file()
197
197
198 # save kwargs for use in restart
198 # save kwargs for use in restart
199 self._launch_args = kw.copy()
199 self._launch_args = kw.copy()
200 # build the Popen cmd
200 # build the Popen cmd
201 kernel_cmd = self.format_kernel_cmd(**kw)
201 kernel_cmd = self.format_kernel_cmd(**kw)
202 # launch the kernel subprocess
202 # launch the kernel subprocess
203 self.kernel = self._launch_kernel(kernel_cmd,
203 self.kernel = self._launch_kernel(kernel_cmd,
204 ipython_kernel=self.ipython_kernel,
204 ipython_kernel=self.ipython_kernel,
205 **kw)
205 **kw)
206 self.start_restarter()
206 self.start_restarter()
207 self._connect_control_socket()
207 self._connect_control_socket()
208
208
209 def _send_shutdown_request(self, restart=False):
209 def _send_shutdown_request(self, restart=False):
210 """TODO: send a shutdown request via control channel"""
210 """TODO: send a shutdown request via control channel"""
211 content = dict(restart=restart)
211 content = dict(restart=restart)
212 msg = self.session.msg("shutdown_request", content=content)
212 msg = self.session.msg("shutdown_request", content=content)
213 self.session.send(self._control_socket, msg)
213 self.session.send(self._control_socket, msg)
214
214
215 def shutdown_kernel(self, now=False, restart=False):
215 def shutdown_kernel(self, now=False, restart=False):
216 """Attempts to the stop the kernel process cleanly.
216 """Attempts to the stop the kernel process cleanly.
217
217
218 This attempts to shutdown the kernels cleanly by:
218 This attempts to shutdown the kernels cleanly by:
219
219
220 1. Sending it a shutdown message over the shell channel.
220 1. Sending it a shutdown message over the shell channel.
221 2. If that fails, the kernel is shutdown forcibly by sending it
221 2. If that fails, the kernel is shutdown forcibly by sending it
222 a signal.
222 a signal.
223
223
224 Parameters:
224 Parameters:
225 -----------
225 -----------
226 now : bool
226 now : bool
227 Should the kernel be forcible killed *now*. This skips the
227 Should the kernel be forcible killed *now*. This skips the
228 first, nice shutdown attempt.
228 first, nice shutdown attempt.
229 restart: bool
229 restart: bool
230 Will this kernel be restarted after it is shutdown. When this
230 Will this kernel be restarted after it is shutdown. When this
231 is True, connection files will not be cleaned up.
231 is True, connection files will not be cleaned up.
232 """
232 """
233 # Stop monitoring for restarting while we shutdown.
233 # Stop monitoring for restarting while we shutdown.
234 self.stop_restarter()
234 self.stop_restarter()
235
235
236 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
236 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
237 if sys.platform == 'win32':
237 if sys.platform == 'win32':
238 self._kill_kernel()
238 self._kill_kernel()
239 return
239 return
240
240
241 if now:
241 if now:
242 if self.has_kernel:
242 if self.has_kernel:
243 self._kill_kernel()
243 self._kill_kernel()
244 else:
244 else:
245 # Don't send any additional kernel kill messages immediately, to give
245 # Don't send any additional kernel kill messages immediately, to give
246 # the kernel a chance to properly execute shutdown actions. Wait for at
246 # the kernel a chance to properly execute shutdown actions. Wait for at
247 # most 1s, checking every 0.1s.
247 # most 1s, checking every 0.1s.
248 self._send_shutdown_request(restart=restart)
248 self._send_shutdown_request(restart=restart)
249 for i in range(10):
249 for i in range(10):
250 if self.is_alive():
250 if self.is_alive():
251 time.sleep(0.1)
251 time.sleep(0.1)
252 else:
252 else:
253 break
253 break
254 else:
254 else:
255 # OK, we've waited long enough.
255 # OK, we've waited long enough.
256 if self.has_kernel:
256 if self.has_kernel:
257 self._kill_kernel()
257 self._kill_kernel()
258
258
259 if not restart:
259 if not restart:
260 self.cleanup_connection_file()
260 self.cleanup_connection_file()
261 self.cleanup_ipc_files()
261 self.cleanup_ipc_files()
262 else:
262 else:
263 self.cleanup_ipc_files()
263 self.cleanup_ipc_files()
264
264
265 def restart_kernel(self, now=False, **kw):
265 def restart_kernel(self, now=False, **kw):
266 """Restarts a kernel with the arguments that were used to launch it.
266 """Restarts a kernel with the arguments that were used to launch it.
267
267
268 If the old kernel was launched with random ports, the same ports will be
268 If the old kernel was launched with random ports, the same ports will be
269 used for the new kernel. The same connection file is used again.
269 used for the new kernel. The same connection file is used again.
270
270
271 Parameters
271 Parameters
272 ----------
272 ----------
273 now : bool, optional
273 now : bool, optional
274 If True, the kernel is forcefully restarted *immediately*, without
274 If True, the kernel is forcefully restarted *immediately*, without
275 having a chance to do any cleanup action. Otherwise the kernel is
275 having a chance to do any cleanup action. Otherwise the kernel is
276 given 1s to clean up before a forceful restart is issued.
276 given 1s to clean up before a forceful restart is issued.
277
277
278 In all cases the kernel is restarted, the only difference is whether
278 In all cases the kernel is restarted, the only difference is whether
279 it is given a chance to perform a clean shutdown or not.
279 it is given a chance to perform a clean shutdown or not.
280
280
281 **kw : optional
281 **kw : optional
282 Any options specified here will overwrite those used to launch the
282 Any options specified here will overwrite those used to launch the
283 kernel.
283 kernel.
284 """
284 """
285 if self._launch_args is None:
285 if self._launch_args is None:
286 raise RuntimeError("Cannot restart the kernel. "
286 raise RuntimeError("Cannot restart the kernel. "
287 "No previous call to 'start_kernel'.")
287 "No previous call to 'start_kernel'.")
288 else:
288 else:
289 # Stop currently running kernel.
289 # Stop currently running kernel.
290 self.shutdown_kernel(now=now, restart=True)
290 self.shutdown_kernel(now=now, restart=True)
291
291
292 # Start new kernel.
292 # Start new kernel.
293 self._launch_args.update(kw)
293 self._launch_args.update(kw)
294 self.start_kernel(**self._launch_args)
294 self.start_kernel(**self._launch_args)
295
295
296 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
296 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
297 # unless there is some delay here.
297 # unless there is some delay here.
298 if sys.platform == 'win32':
298 if sys.platform == 'win32':
299 time.sleep(0.2)
299 time.sleep(0.2)
300
300
301 @property
301 @property
302 def has_kernel(self):
302 def has_kernel(self):
303 """Has a kernel been started that we are managing."""
303 """Has a kernel been started that we are managing."""
304 return self.kernel is not None
304 return self.kernel is not None
305
305
306 def _kill_kernel(self):
306 def _kill_kernel(self):
307 """Kill the running kernel.
307 """Kill the running kernel.
308
308
309 This is a private method, callers should use shutdown_kernel(now=True).
309 This is a private method, callers should use shutdown_kernel(now=True).
310 """
310 """
311 if self.has_kernel:
311 if self.has_kernel:
312
312
313 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
313 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
314 # TerminateProcess() on Win32).
314 # TerminateProcess() on Win32).
315 try:
315 try:
316 self.kernel.kill()
316 self.kernel.kill()
317 except OSError as e:
317 except OSError as e:
318 # In Windows, we will get an Access Denied error if the process
318 # In Windows, we will get an Access Denied error if the process
319 # has already terminated. Ignore it.
319 # has already terminated. Ignore it.
320 if sys.platform == 'win32':
320 if sys.platform == 'win32':
321 if e.winerror != 5:
321 if e.winerror != 5:
322 raise
322 raise
323 # On Unix, we may get an ESRCH error if the process has already
323 # On Unix, we may get an ESRCH error if the process has already
324 # terminated. Ignore it.
324 # terminated. Ignore it.
325 else:
325 else:
326 from errno import ESRCH
326 from errno import ESRCH
327 if e.errno != ESRCH:
327 if e.errno != ESRCH:
328 raise
328 raise
329
329
330 # Block until the kernel terminates.
330 # Block until the kernel terminates.
331 self.kernel.wait()
331 self.kernel.wait()
332 self.kernel = None
332 self.kernel = None
333 else:
333 else:
334 raise RuntimeError("Cannot kill kernel. No kernel is running!")
334 raise RuntimeError("Cannot kill kernel. No kernel is running!")
335
335
336 def interrupt_kernel(self):
336 def interrupt_kernel(self):
337 """Interrupts the kernel by sending it a signal.
337 """Interrupts the kernel by sending it a signal.
338
338
339 Unlike ``signal_kernel``, this operation is well supported on all
339 Unlike ``signal_kernel``, this operation is well supported on all
340 platforms.
340 platforms.
341 """
341 """
342 if self.has_kernel:
342 if self.has_kernel:
343 if sys.platform == 'win32':
343 if sys.platform == 'win32':
344 from .zmq.parentpoller import ParentPollerWindows as Poller
344 from .zmq.parentpoller import ParentPollerWindows as Poller
345 Poller.send_interrupt(self.kernel.win32_interrupt_event)
345 Poller.send_interrupt(self.kernel.win32_interrupt_event)
346 else:
346 else:
347 self.kernel.send_signal(signal.SIGINT)
347 self.kernel.send_signal(signal.SIGINT)
348 else:
348 else:
349 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
349 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
350
350
351 def signal_kernel(self, signum):
351 def signal_kernel(self, signum):
352 """Sends a signal to the kernel.
352 """Sends a signal to the kernel.
353
353
354 Note that since only SIGTERM is supported on Windows, this function is
354 Note that since only SIGTERM is supported on Windows, this function is
355 only useful on Unix systems.
355 only useful on Unix systems.
356 """
356 """
357 if self.has_kernel:
357 if self.has_kernel:
358 self.kernel.send_signal(signum)
358 self.kernel.send_signal(signum)
359 else:
359 else:
360 raise RuntimeError("Cannot signal kernel. No kernel is running!")
360 raise RuntimeError("Cannot signal kernel. No kernel is running!")
361
361
362 def is_alive(self):
362 def is_alive(self):
363 """Is the kernel process still running?"""
363 """Is the kernel process still running?"""
364 if self.has_kernel:
364 if self.has_kernel:
365 if self.kernel.poll() is None:
365 if self.kernel.poll() is None:
366 return True
366 return True
367 else:
367 else:
368 return False
368 return False
369 else:
369 else:
370 # we don't have a kernel
370 # we don't have a kernel
371 return False
371 return False
372
372
373
373
374 #-----------------------------------------------------------------------------
374 #-----------------------------------------------------------------------------
375 # ABC Registration
375 # ABC Registration
376 #-----------------------------------------------------------------------------
376 #-----------------------------------------------------------------------------
377
377
378 KernelManagerABC.register(KernelManager)
378 KernelManagerABC.register(KernelManager)
379
379
General Comments 0
You need to be logged in to leave comments. Login now