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