mirror of
https://github.com/jupyter/notebook.git
synced 2025-04-12 14:00:27 +08:00
Merge pull request #1609 from minrk/sigterm
exit notebook cleanly on SIGINT, SIGTERM, and add a safety (text) dialog to ask for exit confirmation. This prevents an accidental Ctrl-C in the wrong window from destroying a user's potentially large set of notebooks that could have taken a long time to create. Closes #1601
This commit is contained in:
commit
af3dd115a3
@ -20,10 +20,13 @@ Authors:
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import select
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
|
||||
# Third party
|
||||
@ -449,11 +452,73 @@ class NotebookApp(BaseIPythonApplication):
|
||||
self.port = port
|
||||
break
|
||||
|
||||
def init_signal(self):
|
||||
# FIXME: remove this check when pyzmq dependency is >= 2.1.11
|
||||
# safely extract zmq version info:
|
||||
try:
|
||||
zmq_v = zmq.pyzmq_version_info()
|
||||
except AttributeError:
|
||||
zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
|
||||
if 'dev' in zmq.__version__:
|
||||
zmq_v.append(999)
|
||||
zmq_v = tuple(zmq_v)
|
||||
if zmq_v >= (2,1,9):
|
||||
# This won't work with 2.1.7 and
|
||||
# 2.1.9-10 will log ugly 'Interrupted system call' messages,
|
||||
# but it will work
|
||||
signal.signal(signal.SIGINT, self._handle_sigint)
|
||||
signal.signal(signal.SIGTERM, self._signal_stop)
|
||||
|
||||
def _handle_sigint(self, sig, frame):
|
||||
"""SIGINT handler spawns confirmation dialog"""
|
||||
# register more forceful signal handler for ^C^C case
|
||||
signal.signal(signal.SIGINT, self._signal_stop)
|
||||
# request confirmation dialog in bg thread, to avoid
|
||||
# blocking the App
|
||||
thread = threading.Thread(target=self._confirm_exit)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
def _restore_sigint_handler(self):
|
||||
"""callback for restoring original SIGINT handler"""
|
||||
signal.signal(signal.SIGINT, self._handle_sigint)
|
||||
|
||||
def _confirm_exit(self):
|
||||
"""confirm shutdown on ^C
|
||||
|
||||
A second ^C, or answering 'y' within 5s will cause shutdown,
|
||||
otherwise original SIGINT handler will be restored.
|
||||
"""
|
||||
# FIXME: remove this delay when pyzmq dependency is >= 2.1.11
|
||||
time.sleep(0.1)
|
||||
sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
|
||||
sys.stdout.flush()
|
||||
r,w,x = select.select([sys.stdin], [], [], 5)
|
||||
if r:
|
||||
line = sys.stdin.readline()
|
||||
if line.lower().startswith('y'):
|
||||
self.log.critical("Shutdown confirmed")
|
||||
ioloop.IOLoop.instance().stop()
|
||||
return
|
||||
else:
|
||||
print "No answer for 5s:",
|
||||
print "resuming operation..."
|
||||
# no answer, or answer is no:
|
||||
# set it back to original SIGINT handler
|
||||
# use IOLoop.add_callback because signal.signal must be called
|
||||
# from main thread
|
||||
ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
|
||||
|
||||
def _signal_stop(self, sig, frame):
|
||||
self.log.critical("received signal %s, stopping", sig)
|
||||
ioloop.IOLoop.instance().stop()
|
||||
|
||||
@catch_config_error
|
||||
def initialize(self, argv=None):
|
||||
super(NotebookApp, self).initialize(argv)
|
||||
self.init_configurables()
|
||||
self.init_webapp()
|
||||
self.init_signal()
|
||||
|
||||
def cleanup_kernels(self):
|
||||
"""shutdown all kernels
|
||||
|
Loading…
x
Reference in New Issue
Block a user