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