Skip to content

Commit 42d2c2c

Browse files
authored
Use KernelManager's graceful shutdown rather than KILLing kernels (#64)
Use KernelManager's graceful shutdown rather than KILLing kernels by default when reset_kc is True
1 parent 47ec61d commit 42d2c2c

File tree

1 file changed

+41
-15
lines changed

1 file changed

+41
-15
lines changed

nbclient/client.py

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import base64
1+
import atexit
22
import collections
33
import datetime
4+
import base64
5+
import signal
46
from textwrap import dedent
57

68
from async_generator import asynccontextmanager
@@ -278,7 +280,7 @@ def __init__(self, nb, km=None, **kw):
278280
----------
279281
nb : NotebookNode
280282
Notebook being executed.
281-
km : KernerlManager (optional)
283+
km : KernelManager (optional)
282284
Optional kernel manager. If none is provided, a kernel manager will
283285
be created.
284286
"""
@@ -330,23 +332,21 @@ def start_kernel_manager(self):
330332
return self.km
331333

332334
async def _async_cleanup_kernel(self):
335+
now = self.shutdown_kernel == "immediate"
333336
try:
334-
# Send a polite shutdown request
335-
await ensure_async(self.kc.shutdown())
336-
try:
337-
# Queue the manager to kill the process, sometimes the built-in and above
338-
# shutdowns have not been successful or called yet, so give a direct kill
339-
# call here and recover gracefully if it's already dead.
340-
await ensure_async(self.km.shutdown_kernel(now=True))
341-
except RuntimeError as e:
342-
# The error isn't specialized, so we have to check the message
343-
if 'No kernel is running!' not in str(e):
344-
raise
337+
# Queue the manager to kill the process, and recover gracefully if it's already dead.
338+
if await ensure_async(self.km.is_alive()):
339+
await ensure_async(self.km.shutdown_kernel(now=now))
340+
except RuntimeError as e:
341+
# The error isn't specialized, so we have to check the message
342+
if 'No kernel is running!' not in str(e):
343+
raise
345344
finally:
346345
# Remove any state left over even if we failed to stop the kernel
347346
await ensure_async(self.km.cleanup())
348-
await ensure_async(self.kc.stop_channels())
349-
self.kc = None
347+
if getattr(self, "kc"):
348+
await ensure_async(self.kc.stop_channels())
349+
self.kc = None
350350

351351
_cleanup_kernel = run_sync(_async_cleanup_kernel)
352352

@@ -438,11 +438,30 @@ async def async_setup_kernel(self, **kwargs):
438438
439439
When control returns from the yield it stops the client's zmq channels, and shuts
440440
down the kernel.
441+
442+
Handlers for SIGINT and SIGTERM are also added to cleanup in case of unexpected shutdown.
441443
"""
442444
cleanup_kc = kwargs.pop('cleanup_kc', True)
443445
if self.km is None:
444446
self.start_kernel_manager()
445447

448+
# self._cleanup_kernel uses run_async, which ensures the ioloop is running again.
449+
# This is necessary as the ioloop has stopped once atexit fires.
450+
atexit.register(self._cleanup_kernel)
451+
452+
def on_signal():
453+
asyncio.ensure_future(self._async_cleanup_kernel())
454+
atexit.unregister(self._cleanup_kernel)
455+
456+
loop = asyncio.get_event_loop()
457+
try:
458+
loop.add_signal_handler(signal.SIGINT, on_signal)
459+
loop.add_signal_handler(signal.SIGTERM, on_signal)
460+
except (NotImplementedError, RuntimeError):
461+
# NotImplementedError: Windows does not support signals.
462+
# RuntimeError: Raised when add_signal_handler is called outside the main thread
463+
pass
464+
446465
if not self.km.has_kernel:
447466
await self.async_start_new_kernel_client(**kwargs)
448467
try:
@@ -451,6 +470,13 @@ async def async_setup_kernel(self, **kwargs):
451470
if cleanup_kc:
452471
await self._async_cleanup_kernel()
453472

473+
atexit.unregister(self._cleanup_kernel)
474+
try:
475+
loop.remove_signal_handler(signal.SIGINT)
476+
loop.remove_signal_handler(signal.SIGTERM)
477+
except (NotImplementedError, RuntimeError):
478+
pass
479+
454480
async def async_execute(self, reset_kc=False, **kwargs):
455481
"""
456482
Executes each code cell.

0 commit comments

Comments
 (0)