manager.py
413 lines
| 14.1 KiB
| text/x-python
|
PythonLexer
MinRK
|
r12591 | """Base class to manage a running kernel""" | ||
Brian Granger
|
r2606 | |||
Thomas Kluyver
|
r16349 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Brian Granger
|
r2699 | |||
MinRK
|
r9372 | from __future__ import absolute_import | ||
MinRK
|
r9370 | # Standard library imports | ||
Thomas Kluyver
|
r16349 | import os | ||
MinRK
|
r12873 | import re | ||
epatters
|
r3027 | import signal | ||
epatters
|
r2995 | import sys | ||
epatters
|
r2614 | import time | ||
Thomas Kluyver
|
r16263 | import warnings | ||
Brian Granger
|
r2606 | |||
import zmq | ||||
epatters
|
r2611 | |||
MinRK
|
r9370 | # Local imports | ||
MinRK
|
r10285 | from IPython.config.configurable import LoggingConfigurable | ||
MinRK
|
r10300 | from IPython.utils.importstring import import_item | ||
MinRK
|
r12591 | from IPython.utils.localinterfaces import is_local_ip, local_ips | ||
Thomas Kluyver
|
r16382 | from IPython.utils.path import get_ipython_dir | ||
MinRK
|
r4958 | from IPython.utils.traitlets import ( | ||
MinRK
|
r10300 | Any, Instance, Unicode, List, Bool, Type, DottedObjectName | ||
MinRK
|
r4958 | ) | ||
MinRK
|
r9353 | from IPython.kernel import ( | ||
MinRK
|
r9350 | make_ipkernel_cmd, | ||
MinRK
|
r9348 | launch_kernel, | ||
Thomas Kluyver
|
r16263 | kernelspec, | ||
MinRK
|
r9348 | ) | ||
MinRK
|
r10285 | from .connect import ConnectionFileMixin | ||
MinRK
|
r9376 | from .zmq.session import Session | ||
MinRK
|
r10284 | from .managerabc import ( | ||
Brian Granger
|
r9121 | KernelManagerABC | ||
) | ||||
Brian Granger
|
r2699 | |||
MinRK
|
r10285 | class KernelManager(LoggingConfigurable, ConnectionFileMixin): | ||
"""Manages a single kernel in a subprocess on this host. | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r10285 | This version starts kernels with Popen. | ||
epatters
|
r2631 | """ | ||
MinRK
|
r10285 | |||
epatters
|
r2611 | # The PyZMQ Context to use for communication with the kernel. | ||
MinRK
|
r4015 | context = Instance(zmq.Context) | ||
def _context_default(self): | ||||
return zmq.Context.instance() | ||||
epatters
|
r2611 | |||
# The Session to use for communication with the kernel. | ||||
MinRK
|
r4015 | session = Instance(Session) | ||
epatters
|
r8408 | def _session_default(self): | ||
MinRK
|
r11064 | return Session(parent=self) | ||
epatters
|
r2611 | |||
MinRK
|
r10300 | # the class to create with our `client` method | ||
MinRK
|
r10384 | client_class = DottedObjectName('IPython.kernel.blocking.BlockingKernelClient') | ||
MinRK
|
r10300 | client_factory = Type() | ||
def _client_class_changed(self, name, old, new): | ||||
self.client_factory = import_item(str(new)) | ||||
epatters
|
r2730 | # The kernel process with which the KernelManager is communicating. | ||
MinRK
|
r9348 | # generally a Popen instance | ||
MinRK
|
r10283 | kernel = Any() | ||
Thomas Kluyver
|
r16263 | |||
Thomas Kluyver
|
r16382 | kernel_spec_manager = Instance(kernelspec.KernelSpecManager) | ||
def _kernel_spec_manager_default(self): | ||||
return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir) | ||||
Thomas Kluyver
|
r16333 | kernel_name = Unicode('python') | ||
Thomas Kluyver
|
r16263 | |||
kernel_spec = Instance(kernelspec.KernelSpec) | ||||
def _kernel_spec_default(self): | ||||
Thomas Kluyver
|
r16382 | return self.kernel_spec_manager.get_kernel_spec(self.kernel_name) | ||
Thomas Kluyver
|
r16263 | |||
def _kernel_name_changed(self, name, old, new): | ||||
Thomas Kluyver
|
r16382 | self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new) | ||
Thomas Kluyver
|
r16333 | self.ipython_kernel = new in {'python', 'python2', 'python3'} | ||
MinRK
|
r10283 | |||
MinRK
|
r9348 | kernel_cmd = List(Unicode, config=True, | ||
Thomas Kluyver
|
r16263 | help="""DEPRECATED: Use kernel_name instead. | ||
The Popen Command to launch the kernel. | ||||
Konrad Hinsen
|
r14820 | Override this if you have a custom kernel. | ||
If kernel_cmd is specified in a configuration file, | ||||
IPython does not pass any arguments to the kernel, | ||||
because it cannot make any assumptions about the | ||||
arguments that the kernel understands. In particular, | ||||
this means that the kernel does not receive the | ||||
option --debug if it given on the IPython command line. | ||||
MinRK
|
r9348 | """ | ||
) | ||||
Brian Granger
|
r10282 | |||
MinRK
|
r9348 | def _kernel_cmd_changed(self, name, old, new): | ||
Thomas Kluyver
|
r16263 | warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to " | ||
"start different kernels.") | ||||
MinRK
|
r9348 | self.ipython_kernel = False | ||
ipython_kernel = Bool(True) | ||||
Thomas Kluyver
|
r16382 | |||
ipython_dir = Unicode() | ||||
def _ipython_dir_default(self): | ||||
return get_ipython_dir() | ||||
epatters
|
r2730 | |||
MinRK
|
r10285 | # Protected traits | ||
MinRK
|
r10296 | _launch_args = Any() | ||
_control_socket = Any() | ||||
MinRK
|
r9175 | |||
MinRK
|
r10309 | _restarter = Any() | ||
Brian Granger
|
r10282 | autorestart = Bool(False, config=True, | ||
help="""Should we autorestart the kernel if it dies.""" | ||||
) | ||||
MinRK
|
r9175 | def __del__(self): | ||
MinRK
|
r10296 | self._close_control_socket() | ||
Brian E. Granger
|
r9154 | self.cleanup_connection_file() | ||
Brian E. Granger
|
r9152 | |||
epatters
|
r2758 | #-------------------------------------------------------------------------- | ||
Brian Granger
|
r10282 | # Kernel restarter | ||
#-------------------------------------------------------------------------- | ||||
def start_restarter(self): | ||||
pass | ||||
def stop_restarter(self): | ||||
pass | ||||
MinRK
|
r10313 | def add_restart_callback(self, callback, event='restart'): | ||
"""register a callback to be called when a kernel is restarted""" | ||||
if self._restarter is None: | ||||
return | ||||
self._restarter.add_callback(callback, event) | ||||
def remove_restart_callback(self, callback, event='restart'): | ||||
"""unregister a callback to be called when a kernel is restarted""" | ||||
if self._restarter is None: | ||||
return | ||||
self._restarter.remove_callback(callback, event) | ||||
Brian Granger
|
r10282 | #-------------------------------------------------------------------------- | ||
MinRK
|
r10300 | # create a Client connected to our Kernel | ||
#-------------------------------------------------------------------------- | ||||
def client(self, **kwargs): | ||||
"""Create a client configured to connect to our kernel""" | ||||
if self.client_factory is None: | ||||
self.client_factory = import_item(self.client_class) | ||||
kw = {} | ||||
kw.update(self.get_connection_info()) | ||||
MinRK
|
r10309 | kw.update(dict( | ||
connection_file=self.connection_file, | ||||
session=self.session, | ||||
MinRK
|
r11064 | parent=self, | ||
MinRK
|
r10309 | )) | ||
MinRK
|
r10300 | |||
# add kwargs last, for manual overrides | ||||
kw.update(kwargs) | ||||
return self.client_factory(**kw) | ||||
#-------------------------------------------------------------------------- | ||||
Brian E. Granger
|
r9151 | # Kernel management | ||
Brian Granger
|
r9121 | #-------------------------------------------------------------------------- | ||
Brian Granger
|
r10282 | |||
MinRK
|
r9348 | def format_kernel_cmd(self, **kw): | ||
MinRK
|
r12873 | """replace templated args (e.g. {connection_file})""" | ||
MinRK
|
r9348 | if self.kernel_cmd: | ||
cmd = self.kernel_cmd | ||||
Thomas Kluyver
|
r16333 | elif self.kernel_name == 'python': | ||
Thomas Kluyver
|
r16263 | # The native kernel gets special handling | ||
MinRK
|
r9350 | cmd = make_ipkernel_cmd( | ||
MinRK
|
r9372 | 'from IPython.kernel.zmq.kernelapp import main; main()', | ||
MinRK
|
r9348 | **kw | ||
) | ||||
Thomas Kluyver
|
r16263 | else: | ||
cmd = self.kernel_spec.argv | ||||
MinRK
|
r9348 | ns = dict(connection_file=self.connection_file) | ||
ns.update(self._launch_args) | ||||
MinRK
|
r12873 | |||
pat = re.compile(r'\{([A-Za-z0-9_]+)\}') | ||||
def from_ns(match): | ||||
"""Get the key out of ns if it's there, otherwise no change.""" | ||||
return ns.get(match.group(1), match.group()) | ||||
return [ pat.sub(from_ns, arg) for arg in cmd ] | ||||
MinRK
|
r10283 | |||
MinRK
|
r9349 | def _launch_kernel(self, kernel_cmd, **kw): | ||
"""actually launch the kernel | ||||
MinRK
|
r10283 | |||
MinRK
|
r9349 | override in a subclass to launch kernel subprocesses differently | ||
""" | ||||
return launch_kernel(kernel_cmd, **kw) | ||||
MinRK
|
r10283 | |||
MinRK
|
r10300 | # Control socket used for polite kernel shutdown | ||
MinRK
|
r10296 | def _connect_control_socket(self): | ||
if self._control_socket is None: | ||||
self._control_socket = self.connect_control() | ||||
MinRK
|
r10300 | self._control_socket.linger = 100 | ||
MinRK
|
r10296 | |||
def _close_control_socket(self): | ||||
if self._control_socket is None: | ||||
return | ||||
self._control_socket.close() | ||||
self._control_socket = None | ||||
epatters
|
r2851 | def start_kernel(self, **kw): | ||
Brian E. Granger
|
r9129 | """Starts a kernel on this host in a separate process. | ||
epatters
|
r2686 | |||
Brian Granger
|
r2699 | If random ports (port=0) are being used, this method must be called | ||
before the channels are created. | ||||
epatters
|
r2758 | |||
Thomas Kluyver
|
r13587 | Parameters | ||
---------- | ||||
epatters
|
r3784 | **kw : optional | ||
MinRK
|
r9352 | keyword arguments that are passed down to build the kernel_cmd | ||
and launching the kernel (e.g. Popen kwargs). | ||||
epatters
|
r2611 | """ | ||
MinRK
|
r12591 | if self.transport == 'tcp' and not is_local_ip(self.ip): | ||
MinRK
|
r3144 | raise RuntimeError("Can only launch a kernel on a local interface. " | ||
epatters
|
r2667 | "Make sure that the '*_address' attributes are " | ||
MinRK
|
r3144 | "configured properly. " | ||
MinRK
|
r12591 | "Currently valid addresses are: %s" % local_ips() | ||
MinRK
|
r3144 | ) | ||
MinRK
|
r10283 | |||
MinRK
|
r4958 | # write connection file / get default ports | ||
self.write_connection_file() | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r9348 | # save kwargs for use in restart | ||
epatters
|
r2851 | self._launch_args = kw.copy() | ||
MinRK
|
r9348 | # build the Popen cmd | ||
kernel_cmd = self.format_kernel_cmd(**kw) | ||||
Thomas Kluyver
|
r16349 | if self.kernel_cmd: | ||
# If kernel_cmd has been set manually, don't refer to a kernel spec | ||||
env = os.environ | ||||
else: | ||||
# Environment variables from kernel spec are added to os.environ | ||||
env = os.environ.copy() | ||||
env.update(self.kernel_spec.env or {}) | ||||
MinRK
|
r9348 | # launch the kernel subprocess | ||
Thomas Kluyver
|
r16349 | self.kernel = self._launch_kernel(kernel_cmd, env=env, | ||
MinRK
|
r9349 | ipython_kernel=self.ipython_kernel, | ||
**kw) | ||||
Brian Granger
|
r10282 | self.start_restarter() | ||
MinRK
|
r10296 | self._connect_control_socket() | ||
epatters
|
r2686 | |||
MinRK
|
r10285 | def _send_shutdown_request(self, restart=False): | ||
"""TODO: send a shutdown request via control channel""" | ||||
MinRK
|
r10296 | content = dict(restart=restart) | ||
msg = self.session.msg("shutdown_request", content=content) | ||||
self.session.send(self._control_socket, msg) | ||||
MinRK
|
r10285 | |||
Brian Granger
|
r9119 | def shutdown_kernel(self, now=False, restart=False): | ||
MinRK
|
r10283 | """Attempts to the stop the kernel process cleanly. | ||
Brian E. Granger
|
r9129 | |||
This attempts to shutdown the kernels cleanly by: | ||||
1. Sending it a shutdown message over the shell channel. | ||||
2. If that fails, the kernel is shutdown forcibly by sending it | ||||
a signal. | ||||
epatters
|
r8487 | |||
Thomas Kluyver
|
r13587 | Parameters | ||
---------- | ||||
Brian E. Granger
|
r9129 | now : bool | ||
Should the kernel be forcible killed *now*. This skips the | ||||
first, nice shutdown attempt. | ||||
restart: bool | ||||
Will this kernel be restarted after it is shutdown. When this | ||||
is True, connection files will not be cleaned up. | ||||
epatters
|
r2961 | """ | ||
Brian Granger
|
r10282 | # Stop monitoring for restarting while we shutdown. | ||
self.stop_restarter() | ||||
# FIXME: Shutdown does not work on Windows due to ZMQ errors! | ||||
MinRK
|
r14806 | if now or sys.platform == 'win32': | ||
epatters
|
r2961 | if self.has_kernel: | ||
Brian E. Granger
|
r9130 | self._kill_kernel() | ||
Brian Granger
|
r9119 | else: | ||
# Don't send any additional kernel kill messages immediately, to give | ||||
# the kernel a chance to properly execute shutdown actions. Wait for at | ||||
# most 1s, checking every 0.1s. | ||||
MinRK
|
r10285 | self._send_shutdown_request(restart=restart) | ||
Brian Granger
|
r9119 | for i in range(10): | ||
Brian E. Granger
|
r10275 | if self.is_alive(): | ||
Brian Granger
|
r9119 | time.sleep(0.1) | ||
else: | ||||
break | ||||
else: | ||||
# OK, we've waited long enough. | ||||
if self.has_kernel: | ||||
Brian E. Granger
|
r9130 | self._kill_kernel() | ||
Bernardo B. Marques
|
r4872 | |||
Brian E. Granger
|
r9117 | if not restart: | ||
self.cleanup_connection_file() | ||||
Brian E. Granger
|
r9151 | self.cleanup_ipc_files() | ||
Brian Granger
|
r9119 | else: | ||
self.cleanup_ipc_files() | ||||
MinRK
|
r14806 | |||
self._close_control_socket() | ||||
MinRK
|
r4958 | |||
epatters
|
r3784 | def restart_kernel(self, now=False, **kw): | ||
"""Restarts a kernel with the arguments that were used to launch it. | ||||
Bernardo B. Marques
|
r4872 | |||
epatters
|
r3784 | If the old kernel was launched with random ports, the same ports will be | ||
Brian E. Granger
|
r9129 | used for the new kernel. The same connection file is used again. | ||
Fernando Perez
|
r2972 | |||
Parameters | ||||
---------- | ||||
Fernando Perez
|
r3030 | now : bool, optional | ||
epatters
|
r3784 | If True, the kernel is forcefully restarted *immediately*, without | ||
having a chance to do any cleanup action. Otherwise the kernel is | ||||
given 1s to clean up before a forceful restart is issued. | ||||
Fernando Perez
|
r2972 | |||
epatters
|
r3784 | In all cases the kernel is restarted, the only difference is whether | ||
it is given a chance to perform a clean shutdown or not. | ||||
**kw : optional | ||||
Brian E. Granger
|
r9129 | Any options specified here will overwrite those used to launch the | ||
epatters
|
r3784 | kernel. | ||
epatters
|
r2851 | """ | ||
if self._launch_args is None: | ||||
raise RuntimeError("Cannot restart the kernel. " | ||||
"No previous call to 'start_kernel'.") | ||||
else: | ||||
epatters
|
r3784 | # Stop currently running kernel. | ||
Brian Granger
|
r9119 | self.shutdown_kernel(now=now, restart=True) | ||
epatters
|
r3784 | |||
# Start new kernel. | ||||
self._launch_args.update(kw) | ||||
epatters
|
r2915 | self.start_kernel(**self._launch_args) | ||
epatters
|
r2851 | |||
epatters
|
r2995 | # FIXME: Messages get dropped in Windows due to probable ZMQ bug | ||
# unless there is some delay here. | ||||
if sys.platform == 'win32': | ||||
time.sleep(0.2) | ||||
epatters
|
r2686 | @property | ||
def has_kernel(self): | ||||
Brian E. Granger
|
r9129 | """Has a kernel been started that we are managing.""" | ||
epatters
|
r2730 | return self.kernel is not None | ||
epatters
|
r2686 | |||
Brian E. Granger
|
r9130 | def _kill_kernel(self): | ||
Brian E. Granger
|
r9129 | """Kill the running kernel. | ||
epatters
|
r8487 | |||
Brian E. Granger
|
r9129 | This is a private method, callers should use shutdown_kernel(now=True). | ||
epatters
|
r8487 | """ | ||
Brian Granger
|
r3026 | if self.has_kernel: | ||
epatters
|
r3032 | |||
epatters
|
r8487 | # Signal the kernel to terminate (sends SIGKILL on Unix and calls | ||
# TerminateProcess() on Win32). | ||||
epatters
|
r3034 | try: | ||
self.kernel.kill() | ||||
Matthias BUSSONNIER
|
r7787 | except OSError as e: | ||
epatters
|
r3034 | # In Windows, we will get an Access Denied error if the process | ||
# has already terminated. Ignore it. | ||||
epatters
|
r3827 | if sys.platform == 'win32': | ||
if e.winerror != 5: | ||||
raise | ||||
# On Unix, we may get an ESRCH error if the process has already | ||||
# terminated. Ignore it. | ||||
else: | ||||
from errno import ESRCH | ||||
if e.errno != ESRCH: | ||||
raise | ||||
epatters
|
r8487 | |||
# Block until the kernel terminates. | ||||
self.kernel.wait() | ||||
epatters
|
r2730 | self.kernel = None | ||
epatters
|
r2639 | else: | ||
epatters
|
r2686 | raise RuntimeError("Cannot kill kernel. No kernel is running!") | ||
epatters
|
r2611 | |||
epatters
|
r3027 | def interrupt_kernel(self): | ||
Brian E. Granger
|
r9129 | """Interrupts the kernel by sending it a signal. | ||
epatters
|
r8487 | |||
Unlike ``signal_kernel``, this operation is well supported on all | ||||
platforms. | ||||
epatters
|
r3027 | """ | ||
if self.has_kernel: | ||||
if sys.platform == 'win32': | ||||
Thomas Kluyver
|
r9487 | from .zmq.parentpoller import ParentPollerWindows as Poller | ||
epatters
|
r3027 | Poller.send_interrupt(self.kernel.win32_interrupt_event) | ||
else: | ||||
self.kernel.send_signal(signal.SIGINT) | ||||
else: | ||||
raise RuntimeError("Cannot interrupt kernel. No kernel is running!") | ||||
epatters
|
r2611 | def signal_kernel(self, signum): | ||
Brian E. Granger
|
r9129 | """Sends a signal to the kernel. | ||
epatters
|
r8487 | |||
Note that since only SIGTERM is supported on Windows, this function is | ||||
only useful on Unix systems. | ||||
epatters
|
r3027 | """ | ||
Brian Granger
|
r3026 | if self.has_kernel: | ||
epatters
|
r2730 | self.kernel.send_signal(signum) | ||
epatters
|
r2686 | else: | ||
raise RuntimeError("Cannot signal kernel. No kernel is running!") | ||||
epatters
|
r2611 | |||
Brian Granger
|
r2699 | def is_alive(self): | ||
"""Is the kernel process still running?""" | ||||
Brian Granger
|
r3026 | if self.has_kernel: | ||
epatters
|
r2730 | if self.kernel.poll() is None: | ||
Brian Granger
|
r2699 | return True | ||
else: | ||||
return False | ||||
else: | ||||
MinRK
|
r10285 | # we don't have a kernel | ||
return False | ||||
Brian Granger
|
r2699 | |||
Brian Granger
|
r9121 | KernelManagerABC.register(KernelManager) | ||
MinRK
|
r10285 | |||