Permalink
Browse files

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.
  • Loading branch information...
2 parents a827fe6 + 0f2a7ee commit e73fe992bf511a076e336dfe8878b1910f064506 @fperez fperez committed Jan 6, 2012
View
@@ -184,6 +184,10 @@ def init_code(self):
self._run_exec_files()
self._run_cmd_line_code()
+ # flush output, so itwon't be attached to the first cell
+ sys.stdout.flush()
+ sys.stderr.flush()
+
# Hide variables defined here from %who etc.
self.shell.user_ns_hidden.update(self.shell.user_ns)
@@ -18,6 +18,7 @@
import logging
import Cookie
+import time
import uuid
from tornado import web
@@ -412,6 +413,7 @@ def on_first_message(self, msg):
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 @@ def start_hb(self, callback):
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 @@ def ping_or_dead():
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 @@ def parse_command_line(self, argv=None):
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 @@ def init_logging(self):
# 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 @@ def initialize(self, argv=None):
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 @@ def initialize(self, argv=None):
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 @@ def start(self):
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()

0 comments on commit e73fe99

Please sign in to comment.