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:
Fernando Perez 2012-04-16 12:32:18 -07:00
commit af3dd115a3

View File

@ -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