Skip to content

Commit 0d7d00f

Browse files
authored
Merge pull request #294 from filmor/interrupt
Additional to the actual signal, send a message on the control port
2 parents 7a0278a + e2772bd commit 0d7d00f

File tree

5 files changed

+55
-14
lines changed

5 files changed

+55
-14
lines changed

docs/kernels.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@ JSON serialised dictionary containing the following keys and values:
132132
is found, a kernel with a matching `language` will be used.
133133
This allows a notebook written on any Python or Julia kernel to be properly associated
134134
with the user's Python or Julia kernel, even if they aren't listed under the same name as the author's.
135+
- **interrupt_mode** (optional): May be either ``signal`` or ``message`` and
136+
specifies how a client is supposed to interrupt cell execution on this kernel,
137+
either by sending an interrupt ``signal`` via the operating system's
138+
signalling facilities (e.g. `SIGINT` on POSIX systems), or by sending an
139+
``interrupt_request`` message on the control channel (see
140+
:ref:`msging_interrupt`). If this is not specified
141+
the client will default to ``signal`` mode.
135142
- **env** (optional): A dictionary of environment variables to set for the kernel.
136143
These will be added to the current environment variables before the kernel is
137144
started.

docs/messaging.rst

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Versioning
2121

2222
The Jupyter message specification is versioned independently of the packages
2323
that use it.
24-
The current version of the specification is 5.2.
24+
The current version of the specification is 5.3.
2525

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

962+
.. _msging_interrupt:
963+
964+
Kernel interrupt
965+
----------------
966+
967+
In case a kernel can not catch operating system interrupt signals (e.g. the used
968+
runtime handles signals and does not allow a user program to define a callback),
969+
a kernel can choose to be notified using a message instead. For this to work,
970+
the kernels kernelspec must set `interrupt_mode` to ``message``. An interruption
971+
will then result in the following message on the `control` channel:
972+
973+
Message type: ``interrupt_request``::
974+
975+
content = {}
976+
977+
Message type: ``interrupt_reply``::
978+
979+
content = {}
980+
981+
.. versionadded:: 5.3
982+
962983

963984
Messages on the IOPub (PUB/SUB) channel
964985
=======================================

jupyter_client/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
version_info = (5, 1, 0)
22
__version__ = '.'.join(map(str, version_info))
33

4-
protocol_version_info = (5, 2)
4+
protocol_version_info = (5, 3)
55
protocol_version = "%i.%i" % protocol_version_info

jupyter_client/kernelspec.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
pjoin = os.path.join
1414

1515
from ipython_genutils.py3compat import PY3
16-
from traitlets import HasTraits, List, Unicode, Dict, Set, Bool, Type
16+
from traitlets import (
17+
HasTraits, List, Unicode, Dict, Set, Bool, Type, CaselessStrEnum
18+
)
1719
from traitlets.config import LoggingConfigurable
1820

1921
from jupyter_core.paths import jupyter_data_dir, jupyter_path, SYSTEM_JUPYTER_PATH
@@ -28,6 +30,9 @@ class KernelSpec(HasTraits):
2830
language = Unicode()
2931
env = Dict()
3032
resource_dir = Unicode()
33+
interrupt_mode = CaselessStrEnum(
34+
['message', 'signal'], default_value='signal'
35+
)
3136
metadata = Dict()
3237

3338
@classmethod
@@ -46,6 +51,7 @@ def to_dict(self):
4651
env=self.env,
4752
display_name=self.display_name,
4853
language=self.language,
54+
interrupt_mode=self.interrupt_mode,
4955
metadata=self.metadata,
5056
)
5157

@@ -227,7 +233,7 @@ def get_all_specs(self):
227233

228234
def remove_kernel_spec(self, name):
229235
"""Remove a kernel spec directory by name.
230-
236+
231237
Returns the path that was deleted.
232238
"""
233239
save_native = self.ensure_native_kernel
@@ -263,7 +269,7 @@ def install_kernel_spec(self, source_dir, kernel_name=None, user=False,
263269
If ``user`` is False, it will attempt to install into the systemwide
264270
kernel registry. If the process does not have appropriate permissions,
265271
an :exc:`OSError` will be raised.
266-
272+
267273
If ``prefix`` is given, the kernelspec will be installed to
268274
PREFIX/share/jupyter/kernels/KERNEL_NAME. This can be sys.prefix
269275
for installation inside virtual or conda envs.
@@ -284,16 +290,16 @@ def install_kernel_spec(self, source_dir, kernel_name=None, user=False,
284290
DeprecationWarning,
285291
stacklevel=2,
286292
)
287-
293+
288294
destination = self._get_destination_dir(kernel_name, user=user, prefix=prefix)
289295
self.log.debug('Installing kernelspec in %s', destination)
290-
296+
291297
kernel_dir = os.path.dirname(destination)
292298
if kernel_dir not in self.kernel_dirs:
293299
self.log.warning("Installing to %s, which is not in %s. The kernelspec may not be found.",
294300
kernel_dir, self.kernel_dirs,
295301
)
296-
302+
297303
if os.path.isdir(destination):
298304
self.log.info('Removing existing kernelspec in %s', destination)
299305
shutil.rmtree(destination)

jupyter_client/manager.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def start_kernel(self, **kw):
246246
env.update(self.kernel_spec.env or {})
247247
elif self.extra_env:
248248
env.update(self.extra_env)
249-
249+
250250
# launch the kernel subprocess
251251
self.log.debug("Starting kernel: %s", kernel_cmd)
252252
self.kernel = self._launch_kernel(kernel_cmd, env=env,
@@ -403,11 +403,18 @@ def interrupt_kernel(self):
403403
platforms.
404404
"""
405405
if self.has_kernel:
406-
if sys.platform == 'win32':
407-
from .win_interrupt import send_interrupt
408-
send_interrupt(self.kernel.win32_interrupt_event)
409-
else:
410-
self.signal_kernel(signal.SIGINT)
406+
interrupt_mode = self.kernel_spec.interrupt_mode
407+
if interrupt_mode == 'signal':
408+
if sys.platform == 'win32':
409+
from .win_interrupt import send_interrupt
410+
send_interrupt(self.kernel.win32_interrupt_event)
411+
else:
412+
self.signal_kernel(signal.SIGINT)
413+
414+
elif interrupt_mode == 'message':
415+
msg = self.session.msg("interrupt_request", content={})
416+
self._connect_control_socket()
417+
self.session.send(self._control_socket, msg)
411418
else:
412419
raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
413420

0 commit comments

Comments
 (0)