Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional to the actual signal, send a message on the control port #294

Merged
merged 4 commits into from
Nov 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/kernels.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ JSON serialised dictionary containing the following keys and values:
is found, a kernel with a matching `language` will be used.
This allows a notebook written on any Python or Julia kernel to be properly associated
with the user's Python or Julia kernel, even if they aren't listed under the same name as the author's.
- **interrupt_mode** (optional): May be either ``signal`` or ``message`` and
specifies how a client is supposed to interrupt cell execution on this kernel,
either by sending an interrupt ``signal`` via the operating system's
signalling facilities (e.g. `SIGINT` on POSIX systems), or by sending an
``interrupt_request`` message on the control channel (see
:ref:`msging_interrupt`). If this is not specified
the client will default to ``signal`` mode.
- **env** (optional): A dictionary of environment variables to set for the kernel.
These will be added to the current environment variables before the kernel is
started.
Expand Down
23 changes: 22 additions & 1 deletion docs/messaging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Versioning

The Jupyter message specification is versioned independently of the packages
that use it.
The current version of the specification is 5.2.
The current version of the specification is 5.3.

.. note::
*New in* and *Changed in* messages in this document refer to versions of the
Expand Down Expand Up @@ -959,6 +959,27 @@ Message type: ``shutdown_reply``::
socket, they simply send a forceful process termination signal, since a dead
process is unlikely to respond in any useful way to messages.

.. _msging_interrupt:

Kernel interrupt
----------------

In case a kernel can not catch operating system interrupt signals (e.g. the used
runtime handles signals and does not allow a user program to define a callback),
a kernel can choose to be notified using a message instead. For this to work,
the kernels kernelspec must set `interrupt_mode` to ``message``. An interruption
will then result in the following message on the `control` channel:

Message type: ``interrupt_request``::

content = {}

Message type: ``interrupt_reply``::

content = {}

.. versionadded:: 5.3


Messages on the IOPub (PUB/SUB) channel
=======================================
Expand Down
2 changes: 1 addition & 1 deletion jupyter_client/_version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
version_info = (5, 1, 0)
__version__ = '.'.join(map(str, version_info))

protocol_version_info = (5, 2)
protocol_version_info = (5, 3)
protocol_version = "%i.%i" % protocol_version_info
18 changes: 12 additions & 6 deletions jupyter_client/kernelspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
pjoin = os.path.join

from ipython_genutils.py3compat import PY3
from traitlets import HasTraits, List, Unicode, Dict, Set, Bool, Type
from traitlets import (
HasTraits, List, Unicode, Dict, Set, Bool, Type, CaselessStrEnum
)
from traitlets.config import LoggingConfigurable

from jupyter_core.paths import jupyter_data_dir, jupyter_path, SYSTEM_JUPYTER_PATH
Expand All @@ -28,6 +30,9 @@ class KernelSpec(HasTraits):
language = Unicode()
env = Dict()
resource_dir = Unicode()
interrupt_mode = CaselessStrEnum(
['message', 'signal'], default_value='signal'
)
metadata = Dict()

@classmethod
Expand All @@ -46,6 +51,7 @@ def to_dict(self):
env=self.env,
display_name=self.display_name,
language=self.language,
interrupt_mode=self.interrupt_mode,
metadata=self.metadata,
)

Expand Down Expand Up @@ -227,7 +233,7 @@ def get_all_specs(self):

def remove_kernel_spec(self, name):
"""Remove a kernel spec directory by name.

Returns the path that was deleted.
"""
save_native = self.ensure_native_kernel
Expand Down Expand Up @@ -263,7 +269,7 @@ def install_kernel_spec(self, source_dir, kernel_name=None, user=False,
If ``user`` is False, it will attempt to install into the systemwide
kernel registry. If the process does not have appropriate permissions,
an :exc:`OSError` will be raised.

If ``prefix`` is given, the kernelspec will be installed to
PREFIX/share/jupyter/kernels/KERNEL_NAME. This can be sys.prefix
for installation inside virtual or conda envs.
Expand All @@ -284,16 +290,16 @@ def install_kernel_spec(self, source_dir, kernel_name=None, user=False,
DeprecationWarning,
stacklevel=2,
)

destination = self._get_destination_dir(kernel_name, user=user, prefix=prefix)
self.log.debug('Installing kernelspec in %s', destination)

kernel_dir = os.path.dirname(destination)
if kernel_dir not in self.kernel_dirs:
self.log.warning("Installing to %s, which is not in %s. The kernelspec may not be found.",
kernel_dir, self.kernel_dirs,
)

if os.path.isdir(destination):
self.log.info('Removing existing kernelspec in %s', destination)
shutil.rmtree(destination)
Expand Down
19 changes: 13 additions & 6 deletions jupyter_client/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def start_kernel(self, **kw):
env.update(self.kernel_spec.env or {})
elif self.extra_env:
env.update(self.extra_env)

# launch the kernel subprocess
self.log.debug("Starting kernel: %s", kernel_cmd)
self.kernel = self._launch_kernel(kernel_cmd, env=env,
Expand Down Expand Up @@ -403,11 +403,18 @@ def interrupt_kernel(self):
platforms.
"""
if self.has_kernel:
if sys.platform == 'win32':
from .win_interrupt import send_interrupt
send_interrupt(self.kernel.win32_interrupt_event)
else:
self.signal_kernel(signal.SIGINT)
interrupt_mode = self.kernel_spec.interrupt_mode
if interrupt_mode == 'signal':
if sys.platform == 'win32':
from .win_interrupt import send_interrupt
send_interrupt(self.kernel.win32_interrupt_event)
else:
self.signal_kernel(signal.SIGINT)

elif interrupt_mode == 'message':
msg = self.session.msg("interrupt_request", content={})
self._connect_control_socket()
self.session.send(self._control_socket, msg)
else:
raise RuntimeError("Cannot interrupt kernel. No kernel is running!")

Expand Down