Merge pull request #1187 from minrk/nbshutdown

Notebook cleanups and fixes: connection file cleanup, first heartbeat, startup flush.

Kernels would not linger, but the KernelManagers are not garbage-collected on shutdown.  This means that connection files for kernels still running at notebook shutdown would not be removed.

Now, kernels are explicitly killed at server shutdown, allowing the KernelManagers to cleanup files.

Small changes along the way:

* disables the unnecessary (and actively detrimental) SIGINT handler inherited from the original copy/paste from the qt app.
* put webapp initialization in `init_webapp` out of `initialize`, to preserve convention of there being no unique code in `initialize()`.
* don't warn about http on all interfaces if running in 100% read-only mode, because no login or execution is possible.

Closes #1232.
Fernando Perez 15 years ago
commit a2bb278c3a

@ -18,6 +18,7 @@ Authors:
import logging
import Cookie
import time
import uuid
from tornado import web
@ -412,6 +413,7 @@ class IOPubHandler(AuthenticatedZMQStreamHandler):
return
km = self.application.kernel_manager
self.time_to_dead = km.time_to_dead
self.first_beat = km.first_beat
kernel_id = self.kernel_id
try:
self.iopub_stream = km.create_iopub_stream(kernel_id)
@ -446,6 +448,7 @@ class IOPubHandler(AuthenticatedZMQStreamHandler):
self._kernel_alive = True
def ping_or_dead():
self.hb_stream.flush()
if self._kernel_alive:
self._kernel_alive = False
self.hb_stream.send(b'ping')
@ -455,25 +458,36 @@ class IOPubHandler(AuthenticatedZMQStreamHandler):
except:
pass
finally:
self._hb_periodic_callback.stop()
self.stop_hb()
def beat_received(msg):
self._kernel_alive = True
self.hb_stream.on_recv(beat_received)
self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
self._hb_periodic_callback.start()
loop = ioloop.IOLoop.instance()
self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000, loop)
loop.add_timeout(time.time()+self.first_beat, self._really_start_hb)
self._beating= True
def _really_start_hb(self):
"""callback for delayed heartbeat start
Only start the hb loop if we haven't been closed during the wait.
"""
if self._beating and not self.hb_stream.closed():
self._hb_periodic_callback.start()
def stop_hb(self):
"""Stop the heartbeating and cancel all related callbacks."""
if self._beating:
self._beating = False
self._hb_periodic_callback.stop()
if not self.hb_stream.closed():
self.hb_stream.on_recv(None)
def kernel_died(self):
self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
self.application.log.error("Kernel %s failed to respond to heartbeat", self.kernel_id)
self.write_message(
{'header': {'msg_type': 'status'},
'parent_header': {},

@ -195,7 +195,10 @@ class MappingKernelManager(MultiKernelManager):
kernel_argv = List(Unicode)
kernel_manager = Instance(KernelManager)
time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
first_beat = Float(5.0, config=True, help="Delay (in seconds) before sending first heartbeat.")
max_msg_size = Integer(65536, config=True, help="""
The max raw message size accepted from the browser
over a WebSocket connection.

@ -303,9 +303,6 @@ class NotebookApp(BaseIPythonApplication):
self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
def init_configurables(self):
# Don't let Qt or ZMQ swallow KeyboardInterupts.
signal.signal(signal.SIGINT, signal.SIG_DFL)
# force Session default to be secure
default_secure(self.config)
# Create a KernelManager and start a kernel.
@ -322,11 +319,9 @@ class NotebookApp(BaseIPythonApplication):
# self.log is a child of. The logging module dipatches log messages to a log
# and all of its ancenstors until propagate is set to False.
self.log.propagate = False
@catch_config_error
def initialize(self, argv=None):
super(NotebookApp, self).initialize(argv)
self.init_configurables()
def init_webapp(self):
"""initialize tornado webapp and httpserver"""
self.web_app = NotebookWebApplication(
self, self.kernel_manager, self.notebook_manager, self.log,
self.webapp_settings
@ -339,7 +334,7 @@ class NotebookApp(BaseIPythonApplication):
ssl_options = None
self.web_app.password = self.password
self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
if ssl_options is None and not self.ip:
if ssl_options is None and not self.ip and not (self.read_only and not self.password):
self.log.critical('WARNING: the notebook server is listening on all IP addresses '
'but not using any encryption or authentication. This is highly '
'insecure and not recommended.')
@ -357,6 +352,24 @@ class NotebookApp(BaseIPythonApplication):
else:
self.port = port
break
@catch_config_error
def initialize(self, argv=None):
super(NotebookApp, self).initialize(argv)
self.init_configurables()
self.init_webapp()
def cleanup_kernels(self):
"""shutdown all kernels
The kernels will shutdown themselves when this process no longer exists,
but explicit shutdown allows the KernelManagers to cleanup the connection files.
"""
self.log.info('Shutting down kernels')
km = self.kernel_manager
# copy list, since kill_kernel deletes keys
for kid in list(km.kernel_ids):
km.kill_kernel(kid)
def start(self):
ip = self.ip if self.ip else '[all ip addresses on your system]'
@ -371,15 +384,20 @@ class NotebookApp(BaseIPythonApplication):
b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
new=2)
threading.Thread(target=b).start()
ioloop.IOLoop.instance().start()
try:
ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
info("Interrupted...")
finally:
self.cleanup_kernels()
#-----------------------------------------------------------------------------
# Main entry point
#-----------------------------------------------------------------------------
def launch_new_instance():
app = NotebookApp()
app = NotebookApp.instance()
app.initialize()
app.start()

Loading…
Cancel
Save