diff --git a/IPython/frontend/html/notebook/handlers.py b/IPython/frontend/html/notebook/handlers.py
index ab680c4..e3a97a1 100644
--- a/IPython/frontend/html/notebook/handlers.py
+++ b/IPython/frontend/html/notebook/handlers.py
@@ -90,14 +90,12 @@ class ZMQStreamHandler(websocket.WebSocketHandler):
rkm = self.application.routing_kernel_manager
self.router = rkm.get_router(kernel_id, self.stream_name)
self.client_id = self.router.register_client(self)
- logging.info("Connection open: %s, %s" % (kernel_id, self.client_id))
def on_message(self, msg):
self.router.forward_msg(self.client_id, msg)
def on_close(self):
self.router.unregister_client(self.client_id)
- logging.info("Connection closed: %s" % self.client_id)
#-----------------------------------------------------------------------------
diff --git a/IPython/frontend/html/notebook/kernelmanager.py b/IPython/frontend/html/notebook/kernelmanager.py
index f5b2ecd..e6735bb 100644
--- a/IPython/frontend/html/notebook/kernelmanager.py
+++ b/IPython/frontend/html/notebook/kernelmanager.py
@@ -207,9 +207,20 @@ class RoutingKernelManager(LoggingConfigurable):
@property
def kernel_ids(self):
+ """List the kernel ids."""
return self.kernel_manager.kernel_ids
+ def kernel_for_notebook(self, notebook_id):
+ """Return the kernel_id for a notebook_id or None."""
+ return self._notebook_mapping.get(notebook_id)
+
+ def set_kernel_for_notebook(self, notebook_id, kernel_id):
+ """Associate a notebook with a kernel."""
+ if notebook_id is not None:
+ self._notebook_mapping[notebook_id] = kernel_id
+
def notebook_for_kernel(self, kernel_id):
+ """Return the notebook_id for a kernel_id or None."""
notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
if len(notebook_ids) == 1:
return notebook_ids[0]
@@ -217,20 +228,29 @@ class RoutingKernelManager(LoggingConfigurable):
return None
def delete_mapping_for_kernel(self, kernel_id):
+ """Remove the kernel/notebook mapping for kernel_id."""
notebook_id = self.notebook_for_kernel(kernel_id)
if notebook_id is not None:
del self._notebook_mapping[notebook_id]
def start_kernel(self, notebook_id=None):
+ """Start a kernel an return its kernel_id.
+
+ Parameters
+ ----------
+ notebook_id : uuid
+ The uuid of the notebook to associate the new kernel with. If this
+ is not None, this kernel will be persistent whenever the notebook
+ requests a kernel.
+ """
self.log.info
- kernel_id = self._notebook_mapping.get(notebook_id)
+ kernel_id = self.kernel_for_notebook(notebook_id)
if kernel_id is None:
kwargs = dict()
kwargs['extra_arguments'] = self.kernel_argv
kernel_id = self.kernel_manager.start_kernel(**kwargs)
- if notebook_id is not None:
- self._notebook_mapping[notebook_id] = kernel_id
- self.log.info("Kernel started for notebook %s: %s" % (notebook_id,kernel_id))
+ self.set_kernel_for_notebook(notebook_id, kernel_id)
+ self.log.info("Kernel started: %s" % kernel_id)
self.log.debug("Kernel args: %r" % kwargs)
self.start_session_manager(kernel_id)
else:
@@ -238,6 +258,7 @@ class RoutingKernelManager(LoggingConfigurable):
return kernel_id
def start_session_manager(self, kernel_id):
+ """Start the ZMQ sockets (a "session") to connect to a kernel."""
sm = self.kernel_manager.create_session_manager(kernel_id)
self._session_dict[kernel_id] = sm
iopub_stream = sm.get_iopub_stream()
@@ -248,10 +269,11 @@ class RoutingKernelManager(LoggingConfigurable):
shell_router = ShellStreamRouter(
zmq_stream=shell_stream, session=sm.session, config=self.config
)
- self._routers[(kernel_id, 'iopub')] = iopub_router
- self._routers[(kernel_id, 'shell')] = shell_router
+ self.set_router(kernel_id, 'iopub', iopub_router)
+ self.set_router(kernel_id, 'shell', shell_router)
def kill_kernel(self, kernel_id):
+ """Kill a kernel and remove its notebook association."""
if kernel_id not in self.kernel_manager:
raise web.HTTPError(404)
try:
@@ -264,12 +286,14 @@ class RoutingKernelManager(LoggingConfigurable):
self.log.info("Kernel killed: %s" % kernel_id)
def interrupt_kernel(self, kernel_id):
+ """Interrupt a kernel."""
if kernel_id not in self.kernel_manager:
raise web.HTTPError(404)
self.kernel_manager.interrupt_kernel(kernel_id)
self.log.debug("Kernel interrupted: %s" % kernel_id)
def restart_kernel(self, kernel_id):
+ """Restart a kernel while keeping clients connected."""
if kernel_id not in self.kernel_manager:
raise web.HTTPError(404)
@@ -286,6 +310,14 @@ class RoutingKernelManager(LoggingConfigurable):
new_iopub_router.copy_clients(old_iopub_router)
new_shell_router.copy_clients(old_shell_router)
+ # Shut down the old routers
+ old_shell_router.close()
+ old_iopub_router.close()
+ self.delete_router(kernel_id, 'shell')
+ self.delete_router(kernel_id, 'iopub')
+ del old_shell_router
+ del old_iopub_router
+
# Now shutdown the old session and the kernel.
# TODO: This causes a hard crash in ZMQStream.close, which sets
# self.socket to None to hastily. We will need to fix this in PyZMQ
@@ -295,12 +327,24 @@ class RoutingKernelManager(LoggingConfigurable):
# Now save the new kernel/notebook association. We have to save it
# after the old kernel is killed as that will delete the mapping.
- self._notebook_mapping[notebook_id] = kernel_id
+ self.set_kernel_for_notebook(notebook_id, new_kernel_id)
- self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
+ self.log.debug("Kernel restarted: %s" % new_kernel_id)
return new_kernel_id
def get_router(self, kernel_id, stream_name):
+ """Return the router for a given kernel_id and stream name."""
router = self._routers[(kernel_id, stream_name)]
return router
+ def set_router(self, kernel_id, stream_name, router):
+ """Set the router for a given kernel_id and stream_name."""
+ self._routers[(kernel_id, stream_name)] = router
+
+ def delete_router(self, kernel_id, stream_name):
+ """Delete a router for a kernel_id and stream_name."""
+ try:
+ del self._routers[(kernel_id, stream_name)]
+ except KeyError:
+ pass
+
diff --git a/IPython/frontend/html/notebook/routers.py b/IPython/frontend/html/notebook/routers.py
index babc8c4..7ff8a0c 100644
--- a/IPython/frontend/html/notebook/routers.py
+++ b/IPython/frontend/html/notebook/routers.py
@@ -37,9 +37,17 @@ class ZMQStreamRouter(Configurable):
super(ZMQStreamRouter,self).__init__(**kwargs)
self.zmq_stream.on_recv(self._on_zmq_reply)
+ def __del__(self):
+ self.close()
+
+ def close(self):
+ """Disable the routing actions of this router."""
+ self._clients = {}
+ self.zmq_stream.on_recv(None)
+
def register_client(self, client):
"""Register a client, returning a client uuid."""
- client_id = uuid.uuid4()
+ client_id = unicode(uuid.uuid4())
self._clients[client_id] = client
return client_id
@@ -112,7 +120,6 @@ class ShellStreamRouter(ZMQStreamRouter):
def forward_msg(self, client_id, msg):
if len(msg) < self.max_msg_size:
msg = json.loads(msg)
- # to_send = self.session.serialize(msg)
self._request_queue.put(client_id)
self.session.send(self.zmq_stream, msg)