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