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