Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Major refactor of kernel connection management in the notebook.

* Full kernel heartbeating is working.
* Connections between the notebook and server and now created
  a new each time there is a WebSocket connection. Each channel is
  also handled separately. This dramatically simplifies the
  server code and makes for a more scalable system.
  • Loading branch information...
commit 5bad195c5e3b1b153464b5a0a45ae57a25802675 1 parent 31e9da5
Brian E. Granger ellisonbg authored
146 IPython/frontend/html/notebook/handlers.py
View
@@ -4,20 +4,22 @@
# Imports
#-----------------------------------------------------------------------------
-import json
-import logging
-import os
-import urllib
from tornado import web
from tornado import websocket
+from zmq.eventloop import ioloop
+from zmq.utils import jsonapi
+
+from IPython.zmq.session import Session
+
try:
from docutils.core import publish_string
except ImportError:
publish_string = None
+
#-----------------------------------------------------------------------------
# Top-level handlers
#-----------------------------------------------------------------------------
@@ -52,15 +54,15 @@ def get(self, notebook_id):
class MainKernelHandler(web.RequestHandler):
def get(self):
- rkm = self.application.routing_kernel_manager
- self.finish(json.dumps(rkm.kernel_ids))
+ km = self.application.kernel_manager
+ self.finish(jsonapi.dumps(km.kernel_ids))
def post(self):
- rkm = self.application.routing_kernel_manager
+ km = self.application.kernel_manager
notebook_id = self.get_argument('notebook', default=None)
- kernel_id = rkm.start_kernel(notebook_id)
+ kernel_id = km.start_kernel(notebook_id)
self.set_header('Location', '/'+kernel_id)
- self.finish(json.dumps(kernel_id))
+ self.finish(jsonapi.dumps(kernel_id))
class KernelHandler(web.RequestHandler):
@@ -68,8 +70,8 @@ class KernelHandler(web.RequestHandler):
SUPPORTED_METHODS = ('DELETE')
def delete(self, kernel_id):
- rkm = self.application.routing_kernel_manager
- rkm.kill_kernel(kernel_id)
+ km = self.application.kernel_manager
+ km.kill_kernel(kernel_id)
self.set_status(204)
self.finish()
@@ -77,31 +79,124 @@ def delete(self, kernel_id):
class KernelActionHandler(web.RequestHandler):
def post(self, kernel_id, action):
- rkm = self.application.routing_kernel_manager
+ km = self.application.kernel_manager
if action == 'interrupt':
- rkm.interrupt_kernel(kernel_id)
+ km.interrupt_kernel(kernel_id)
self.set_status(204)
if action == 'restart':
- new_kernel_id = rkm.restart_kernel(kernel_id)
- self.write(json.dumps(new_kernel_id))
+ new_kernel_id = km.restart_kernel(kernel_id)
+ self.write(jsonapi.dumps(new_kernel_id))
self.finish()
class ZMQStreamHandler(websocket.WebSocketHandler):
- def initialize(self, stream_name):
- self.stream_name = stream_name
+ def _reserialize_reply(self, msg_list):
+ """Reserialize a reply message using JSON.
+
+ This takes the msg list from the ZMQ socket, unserializes it using
+ self.session and then serializes the result using JSON. This method
+ should be used by self._on_zmq_reply to build messages that can
+ be sent back to the browser.
+ """
+ idents, msg_list = self.session.feed_identities(msg_list)
+ msg = self.session.unserialize(msg_list)
+ msg['header'].pop('date')
+ msg.pop('buffers')
+ return jsonapi.dumps(msg)
+
+
+class IOPubHandler(ZMQStreamHandler):
+
+ def initialize(self, *args, **kwargs):
+ self._kernel_alive = True
+ self._beating = False
+
+ def open(self, kernel_id):
+ km = self.application.kernel_manager
+ self.kernel_id = kernel_id
+ self.session = Session()
+ self.time_to_dead = km.time_to_dead
+ self.iopub_stream = km.create_iopub_stream(kernel_id)
+ self.hb_stream = km.create_hb_stream(kernel_id)
+ self.iopub_stream.on_recv(self._on_zmq_reply)
+ self.start_hb(self.kernel_died)
+
+ def _on_zmq_reply(self, msg_list):
+ msg = self._reserialize_reply(msg_list)
+ self.write_message(msg)
+
+ def on_close(self):
+ self.stop_hb()
+ self.iopub_stream.close()
+ self.hb_stream.close()
+
+ def start_hb(self, callback):
+ """Start the heartbeating and call the callback if the kernel dies."""
+ if not self._beating:
+ self._kernel_alive = True
+
+ def ping_or_dead():
+ if self._kernel_alive:
+ self._kernel_alive = False
+ self.hb_stream.send(b'ping')
+ else:
+ try:
+ callback()
+ except:
+ pass
+ finally:
+ self._hb_periodic_callback.stop()
+
+ 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()
+ self._beating= True
+
+ def stop_hb(self):
+ """Stop the heartbeating and cancel all related callbacks."""
+ if self._beating:
+ self._hb_periodic_callback.stop()
+ if not self.hb_stream.closed():
+ self.hb_stream.on_recv(None)
+
+ def kernel_died(self):
+ self.write_message(
+ {'header': {'msg_type': 'status'},
+ 'parent_header': {},
+ 'content': {'execution_state':'dead'}
+ }
+ )
+ self.on_close()
+
+
+class ShellHandler(ZMQStreamHandler):
+
+ def initialize(self, *args, **kwargs):
+ pass
def open(self, kernel_id):
- rkm = self.application.routing_kernel_manager
- self.router = rkm.get_router(kernel_id, self.stream_name)
- self.client_id = self.router.register_client(self)
+ km = self.application.kernel_manager
+ self.max_msg_size = km.max_msg_size
+ self.kernel_id = kernel_id
+ self.session = Session()
+ self.shell_stream = self.application.kernel_manager.create_shell_stream(kernel_id)
+ self.shell_stream.on_recv(self._on_zmq_reply)
+
+ def _on_zmq_reply(self, msg_list):
+ msg = self._reserialize_reply(msg_list)
+ self.write_message(msg)
def on_message(self, msg):
- self.router.forward_msg(self.client_id, msg)
+ if len(msg) < self.max_msg_size:
+ msg = jsonapi.loads(msg)
+ self.session.send(self.shell_stream, msg)
def on_close(self):
- self.router.unregister_client(self.client_id)
+ self.shell_stream.close()
#-----------------------------------------------------------------------------
@@ -113,7 +208,7 @@ class NotebookRootHandler(web.RequestHandler):
def get(self):
nbm = self.application.notebook_manager
files = nbm.list_notebooks()
- self.finish(json.dumps(files))
+ self.finish(jsonapi.dumps(files))
def post(self):
nbm = self.application.notebook_manager
@@ -125,7 +220,7 @@ def post(self):
else:
notebook_id = nbm.new_notebook()
self.set_header('Location', '/'+notebook_id)
- self.finish(json.dumps(notebook_id))
+ self.finish(jsonapi.dumps(notebook_id))
class NotebookHandler(web.RequestHandler):
@@ -175,11 +270,10 @@ def post(self):
body = self.request.body.strip()
source = body
# template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
- print template_path
defaults = {'file_insertion_enabled': 0,
'raw_enabled': 0,
'_disable_config': 1,
- 'stylesheet_path': 0,
+ 'stylesheet_path': 0
# 'template': template_path
}
try:
134 IPython/frontend/html/notebook/kernelmanager.py
View
@@ -16,14 +16,13 @@
import uuid
import zmq
+from zmq.eventloop.zmqstream import ZMQStream
from tornado import web
-from .routers import IOPubStreamRouter, ShellStreamRouter
-
from IPython.config.configurable import LoggingConfigurable
from IPython.zmq.ipkernel import launch_kernel
-from IPython.utils.traitlets import Instance, Dict, List, Unicode
+from IPython.utils.traitlets import Instance, Dict, List, Unicode, Float, Int
#-----------------------------------------------------------------------------
# Classes
@@ -110,6 +109,7 @@ def interrupt_kernel(self, kernel_id):
else:
kernel_process.send_signal(signal.SIGINT)
+
def signal_kernel(self, kernel_id, signum):
""" Sends a signal to the kernel by its uuid.
@@ -182,34 +182,50 @@ def get_kernel_ip(self, kernel_id):
else:
raise KeyError("Kernel with id not found: %s" % kernel_id)
- def create_session_manager(self, kernel_id):
- """Create a new session manager for a kernel by its uuid."""
- from sessionmanager import SessionManager
- return SessionManager(
- kernel_id=kernel_id, kernel_manager=self,
- config=self.config, context=self.context, log=self.log
- )
+ def create_connected_stream(self, ip, port, socket_type):
+ sock = self.context.socket(socket_type)
+ addr = "tcp://%s:%i" % (ip, port)
+ self.log.info("Connecting to: %s" % addr)
+ sock.connect(addr)
+ return ZMQStream(sock)
+
+ def create_iopub_stream(self, kernel_id):
+ ip = self.get_kernel_ip(kernel_id)
+ ports = self.get_kernel_ports(kernel_id)
+ iopub_stream = self.create_connected_stream(ip, ports['iopub_port'], zmq.SUB)
+ iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
+ return iopub_stream
+ def create_shell_stream(self, kernel_id):
+ ip = self.get_kernel_ip(kernel_id)
+ ports = self.get_kernel_ports(kernel_id)
+ shell_stream = self.create_connected_stream(ip, ports['shell_port'], zmq.XREQ)
+ return shell_stream
-class RoutingKernelManager(LoggingConfigurable):
- """A KernelManager that handles WebSocket routing and HTTP error handling"""
+ def create_hb_stream(self, kernel_id):
+ ip = self.get_kernel_ip(kernel_id)
+ ports = self.get_kernel_ports(kernel_id)
+ hb_stream = self.create_connected_stream(ip, ports['hb_port'], zmq.REQ)
+ return hb_stream
+
+
+class MappingKernelManager(KernelManager):
+ """A KernelManager that handles notebok mapping and HTTP error handling"""
kernel_argv = List(Unicode)
kernel_manager = Instance(KernelManager)
+ time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
+ max_msg_size = Int(65536, config=True, help="""
+ The max raw message size accepted from the browser
+ over a WebSocket connection.
+ """)
- _routers = Dict()
- _session_dict = Dict()
_notebook_mapping = Dict()
#-------------------------------------------------------------------------
# Methods for managing kernels and sessions
#-------------------------------------------------------------------------
- @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)
@@ -234,7 +250,7 @@ def delete_mapping_for_kernel(self, kernel_id):
del self._notebook_mapping[notebook_id]
def start_kernel(self, notebook_id=None):
- """Start a kernel an return its kernel_id.
+ """Start a kernel for a notebok an return its kernel_id.
Parameters
----------
@@ -243,108 +259,48 @@ def start_kernel(self, notebook_id=None):
is not None, this kernel will be persistent whenever the notebook
requests a kernel.
"""
- self.log.info
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)
+ kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
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:
self.log.info("Using existing kernel: %s" % kernel_id)
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()
- shell_stream = sm.get_shell_stream()
- iopub_router = IOPubStreamRouter(
- zmq_stream=iopub_stream, session=sm.session, config=self.config
- )
- shell_router = ShellStreamRouter(
- zmq_stream=shell_stream, session=sm.session, config=self.config
- )
- 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:
+ if kernel_id not in self:
raise web.HTTPError(404)
- try:
- sm = self._session_dict.pop(kernel_id)
- except KeyError:
- raise web.HTTPError(404)
- sm.stop()
- self.kernel_manager.kill_kernel(kernel_id)
+ super(MappingKernelManager, self).kill_kernel(kernel_id)
self.delete_mapping_for_kernel(kernel_id)
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:
+ if kernel_id not in self:
raise web.HTTPError(404)
- self.kernel_manager.interrupt_kernel(kernel_id)
- self.log.debug("Kernel interrupted: %s" % kernel_id)
+ super(MappingKernelManager, self).interrupt_kernel(kernel_id)
+ self.log.info("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:
+ if kernel_id not in self:
raise web.HTTPError(404)
- # Get the notebook_id to preserve the kernel/notebook association
+ # Get the notebook_id to preserve the kernel/notebook association.
notebook_id = self.notebook_for_kernel(kernel_id)
# Create the new kernel first so we can move the clients over.
new_kernel_id = self.start_kernel()
-
- # Copy the clients over to the new routers.
- old_iopub_router = self.get_router(kernel_id, 'iopub')
- old_shell_router = self.get_router(kernel_id, 'shell')
- new_iopub_router = self.get_router(new_kernel_id, 'iopub')
- new_shell_router = self.get_router(new_kernel_id, 'shell')
- 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
- # itself. For now, we just leave the old kernel running :(
- # Maybe this is fixed now, but nothing was changed really.
+ # Now kill the old kernel.
self.kill_kernel(kernel_id)
-
# 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.set_kernel_for_notebook(notebook_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
28 IPython/frontend/html/notebook/notebookapp.py
View
@@ -27,12 +27,11 @@
from tornado import httpserver
from tornado import web
-from .kernelmanager import KernelManager, RoutingKernelManager
-from .sessionmanager import SessionManager
+from .kernelmanager import MappingKernelManager
from .handlers import (
NBBrowserHandler, NewHandler, NamedNotebookHandler,
- MainKernelHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
- NotebookRootHandler, NotebookHandler, RSTHandler
+ MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
+ ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
)
from .notebookmanager import NotebookManager
@@ -45,7 +44,7 @@
aliases as ipkernel_aliases,
IPKernelApp
)
-from IPython.utils.traitlets import Dict, Unicode, Int, Any, List, Enum
+from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum
#-----------------------------------------------------------------------------
# Module globals
@@ -71,7 +70,7 @@
class NotebookWebApplication(web.Application):
- def __init__(self, routing_kernel_manager, notebook_manager, log):
+ def __init__(self, kernel_manager, notebook_manager, log):
handlers = [
(r"/", NBBrowserHandler),
(r"/new", NewHandler),
@@ -79,8 +78,8 @@ def __init__(self, routing_kernel_manager, notebook_manager, log):
(r"/kernels", MainKernelHandler),
(r"/kernels/%s" % _kernel_id_regex, KernelHandler),
(r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
- (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
- (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
+ (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
+ (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
(r"/notebooks", NotebookRootHandler),
(r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
(r"/rstservice/render", RSTHandler)
@@ -91,7 +90,7 @@ def __init__(self, routing_kernel_manager, notebook_manager, log):
)
web.Application.__init__(self, handlers, **settings)
- self.routing_kernel_manager = routing_kernel_manager
+ self.kernel_manager = kernel_manager
self.log = log
self.notebook_manager = notebook_manager
@@ -114,7 +113,6 @@ def __init__(self, routing_kernel_manager, notebook_manager, log):
'port': 'IPythonNotebookApp.port',
'keyfile': 'IPythonNotebookApp.keyfile',
'certfile': 'IPythonNotebookApp.certfile',
- 'colors': 'ZMQInteractiveShell.colors',
'notebook-dir': 'NotebookManager.notebook_dir'
})
@@ -136,8 +134,7 @@ class IPythonNotebookApp(BaseIPythonApplication):
examples = _examples
classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
- RoutingKernelManager, NotebookManager,
- KernelManager, SessionManager]
+ MappingKernelManager, NotebookManager]
flags = Dict(flags)
aliases = Dict(aliases)
@@ -187,9 +184,8 @@ def init_configurables(self):
signal.signal(signal.SIGINT, signal.SIG_DFL)
# Create a KernelManager and start a kernel.
- self.kernel_manager = KernelManager(config=self.config, log=self.log)
- self.routing_kernel_manager = RoutingKernelManager(config=self.config, log=self.log,
- kernel_manager=self.kernel_manager, kernel_argv=self.kernel_argv
+ self.kernel_manager = MappingKernelManager(
+ config=self.config, log=self.log, kernel_argv=self.kernel_argv
)
self.notebook_manager = NotebookManager(config=self.config, log=self.log)
@@ -204,7 +200,7 @@ def initialize(self, argv=None):
super(IPythonNotebookApp, self).initialize(argv)
self.init_configurables()
self.web_app = NotebookWebApplication(
- self.routing_kernel_manager, self.notebook_manager, self.log
+ self.kernel_manager, self.notebook_manager, self.log
)
if self.certfile:
ssl_options = dict(certfile=self.certfile)
125 IPython/frontend/html/notebook/routers.py
View
@@ -1,125 +0,0 @@
-"""Routers that connect WebSockets to ZMQ sockets."""
-
-#-----------------------------------------------------------------------------
-# Copyright (C) 2011 The IPython Development Team
-#
-# Distributed under the terms of the BSD License. The full license is in
-# the file COPYING.txt, distributed as part of this software.
-#-----------------------------------------------------------------------------
-
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
-
-import uuid
-from Queue import Queue
-import json
-
-from IPython.config.configurable import Configurable
-from IPython.utils.traitlets import Instance, Int, Dict
-
-#-----------------------------------------------------------------------------
-# Classes
-#-----------------------------------------------------------------------------
-
-class ZMQStreamRouter(Configurable):
-
- zmq_stream = Instance('zmq.eventloop.zmqstream.ZMQStream')
- session = Instance('IPython.zmq.session.Session')
- max_msg_size = Int(2048, config=True, help="""
- The max raw message size accepted from the browser
- over a WebSocket connection.
- """)
-
- _clients = Dict()
-
- def __init__(self, **kwargs):
- 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 = unicode(uuid.uuid4())
- self._clients[client_id] = client
- return client_id
-
- def unregister_client(self, client_id):
- """Unregister a client by its client uuid."""
- del self._clients[client_id]
-
- def copy_clients(self, router):
- """Copy the clients of another router to this one.
-
- This is used to enable the backend zeromq stream to disconnect
- and reconnect while the WebSocket connections to browsers
- remain, such as when a kernel is restarted.
- """
- for client_id, client in router._clients.items():
- client.router = self
- self._clients[client_id] = client
-
- def forward_msg(self, client_id, msg):
- """Forward a msg to a client by its id.
-
- The default implementation of this will fail silently if a message
- arrives on a socket that doesn't support it. This method should
- use max_msg_size to check and silently discard message that are too
- long."""
- pass
-
- def _on_zmq_reply(self, msg_list):
- """Handle a message the ZMQ stream sends to the router.
-
- Usually, this is where the return message will be written to
- clients that need it using client.write_message().
- """
- pass
-
- def _reserialize_reply(self, msg_list):
- """Reserialize a reply message using JSON.
-
- This takes the msg list from the ZMQ socket, unserializes it using
- self.session and then serializes the result using JSON. This method
- should be used by self._on_zmq_reply to build messages that can
- be sent back to the browser.
- """
- idents, msg_list = self.session.feed_identities(msg_list)
- msg = self.session.unserialize(msg_list)
- msg['header'].pop('date')
- msg.pop('buffers')
- return json.dumps(msg)
-
-
-class IOPubStreamRouter(ZMQStreamRouter):
-
- def _on_zmq_reply(self, msg_list):
- msg = self._reserialize_reply(msg_list)
- for client_id, client in self._clients.items():
- client.write_message(msg)
-
-
-class ShellStreamRouter(ZMQStreamRouter):
-
- _request_queue = Instance(Queue,(),{})
-
- def _on_zmq_reply(self, msg_list):
- msg = self._reserialize_reply(msg_list)
- client_id = self._request_queue.get(block=False)
- client = self._clients.get(client_id)
- if client is not None:
- client.write_message(msg)
-
- def forward_msg(self, client_id, msg):
- if len(msg) < self.max_msg_size:
- msg = json.loads(msg)
- self._request_queue.put(client_id)
- self.session.send(self.zmq_stream, msg)
-
90 IPython/frontend/html/notebook/sessionmanager.py
View
@@ -1,90 +0,0 @@
-"""A manager for session and channels for a single kernel."""
-
-#-----------------------------------------------------------------------------
-# Copyright (C) 2011 The IPython Development Team
-#
-# Distributed under the terms of the BSD License. The full license is in
-# the file COPYING.txt, distributed as part of this software.
-#-----------------------------------------------------------------------------
-
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
-
-import zmq
-from zmq.eventloop.zmqstream import ZMQStream
-
-from IPython.utils.traitlets import Instance, Dict, CBytes, Bool
-from IPython.zmq.session import SessionFactory
-
-
-class SessionManagerRunningError(Exception):
- pass
-
-#-----------------------------------------------------------------------------
-# Classes
-#-----------------------------------------------------------------------------
-
-
-class SessionManager(SessionFactory):
- """Manages a session for a kernel.
-
- The object manages a variety of things for a connection session to
- a running kernel:
-
- * The set of channels or connected ZMQ streams to the kernel.
- * An IPython.zmq.session.Session object that manages send/recv logic
- for those channels.
- """
-
- kernel_manager = Instance('IPython.frontend.html.notebook.kernelmanager.KernelManager')
- kernel_id = CBytes(b'')
- _session_streams = Dict()
- _running = Bool(False)
-
- def __init__(self, **kwargs):
- kernel_id = kwargs.pop('kernel_id')
- super(SessionManager, self).__init__(**kwargs)
- self.kernel_id = kernel_id
- self.start()
-
- def __del__(self):
- self.stop()
-
- def start(self):
- if not self._running:
- ports = self.kernel_manager.get_kernel_ports(self.kernel_id)
- iopub_stream = self.create_connected_stream(ports['iopub_port'], zmq.SUB)
- iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
- shell_stream = self.create_connected_stream(ports['shell_port'], zmq.XREQ)
- self._session_streams = dict(
- iopub_stream = iopub_stream,
- shell_stream = shell_stream
- )
- self._running = True
- else:
- raise SessionManagerRunningError(
- 'Session manager is already running, call stop() before start()'
- )
-
- def stop(self):
- if self._running:
- for name, stream in self._session_streams.items():
- stream.close()
- self._session_streams = {}
- self._running = False
-
- def create_connected_stream(self, port, socket_type):
- sock = self.context.socket(socket_type)
- addr = "tcp://%s:%i" % (self.kernel_manager.get_kernel_ip(self.kernel_id), port)
- self.log.info("Connecting to: %s" % addr)
- sock.connect(addr)
- return ZMQStream(sock)
-
- def get_iopub_stream(self):
- return self._session_streams['iopub_stream']
-
- def get_shell_stream(self):
- return self._session_streams['shell_stream']
-
-
85 IPython/frontend/html/notebook/static/js/kernel.js
View
@@ -11,6 +11,9 @@ var IPython = (function (IPython) {
this.kernel_id = null;
this.base_url = "/kernels";
this.kernel_url = null;
+ this.shell_channel = null;
+ this.iopub_channel = null;
+ this.running = false;
};
@@ -28,33 +31,65 @@ var IPython = (function (IPython) {
return msg;
}
- Kernel.prototype.start_kernel = function (notebook_id, callback) {
+ Kernel.prototype.start = function (notebook_id, callback) {
var that = this;
- var qs = $.param({notebook:notebook_id});
- $.post(this.base_url + '?' + qs,
- function (kernel_id) {
- that._handle_start_kernel(kernel_id, callback);
- },
- 'json'
- );
+ if (!this.running) {
+ var qs = $.param({notebook:notebook_id});
+ $.post(this.base_url + '?' + qs,
+ function (kernel_id) {
+ that._handle_start_kernel(kernel_id, callback);
+ },
+ 'json'
+ );
+ };
+ };
+
+
+ Kernel.prototype.restart = function (callback) {
+ IPython.kernel_status_widget.status_restarting();
+ var url = this.kernel_url + "/restart";
+ var that = this;
+ if (this.running) {
+ this.stop_channels();
+ $.post(url,
+ function (kernel_id) {
+ that._handle_start_kernel(kernel_id, callback);
+ },
+ 'json'
+ );
+ };
};
Kernel.prototype._handle_start_kernel = function (kernel_id, callback) {
+ this.running = true;
this.kernel_id = kernel_id;
this.kernel_url = this.base_url + "/" + this.kernel_id;
- this._start_channels();
+ this.start_channels();
callback();
+ IPython.kernel_status_widget.status_idle();
};
- Kernel.prototype._start_channels = function () {
+ Kernel.prototype.start_channels = function () {
+ this.stop_channels();
var ws_url = "ws://127.0.0.1:8888" + this.kernel_url;
this.shell_channel = new WebSocket(ws_url + "/shell");
this.iopub_channel = new WebSocket(ws_url + "/iopub");
- }
+ };
+ Kernel.prototype.stop_channels = function () {
+ if (this.shell_channel !== null) {
+ this.shell_channel.close();
+ this.shell_channel = null;
+ };
+ if (this.iopub_channel !== null) {
+ this.iopub_channel.close();
+ this.iopub_channel = null;
+ };
+ };
+
Kernel.prototype.execute = function (code) {
var content = {
code : code,
@@ -81,29 +116,21 @@ var IPython = (function (IPython) {
Kernel.prototype.interrupt = function () {
- $.post(this.kernel_url + "/interrupt");
- };
-
-
- Kernel.prototype.restart = function () {
- IPython.kernel_status_widget.status_restarting();
- var url = this.kernel_url + "/restart"
- var that = this;
- $.post(url, function (kernel_id) {
- console.log("Kernel restarted: " + kernel_id);
- that.kernel_id = kernel_id;
- that.kernel_url = that.base_url + "/" + that.kernel_id;
- IPython.kernel_status_widget.status_idle();
- }, 'json');
+ if (this.running) {
+ $.post(this.kernel_url + "/interrupt");
+ };
};
Kernel.prototype.kill = function () {
- var settings = {
- cache : false,
- type : "DELETE",
+ if (this.running) {
+ this.running = false;
+ var settings = {
+ cache : false,
+ type : "DELETE",
+ };
+ $.ajax(this.kernel_url, settings);
};
- $.ajax(this.kernel_url, settings);
};
IPython.Kernel = Kernel;
52 IPython/frontend/html/notebook/static/js/notebook.js
View
@@ -482,7 +482,20 @@ var IPython = (function (IPython) {
Notebook.prototype.start_kernel = function () {
this.kernel = new IPython.Kernel();
var notebook_id = IPython.save_widget.get_notebook_id();
- this.kernel.start_kernel(notebook_id, $.proxy(this.kernel_started, this));
+ this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
+ };
+
+
+ Notebook.prototype.restart_kernel = function () {
+ var notebook_id = IPython.save_widget.get_notebook_id();
+ this.kernel.restart($.proxy(this.kernel_started, this));
+ };
+
+
+ Notebook.prototype.kernel_started = function () {
+ console.log("Kernel started: ", this.kernel.kernel_id);
+ this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
+ this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
};
@@ -528,16 +541,41 @@ var IPython = (function (IPython) {
var output_types = ['stream','display_data','pyout','pyerr'];
if (output_types.indexOf(msg_type) >= 0) {
this.handle_output(cell, msg_type, content);
- } else if (msg_type === "status") {
- if (content.execution_state === "busy") {
+ } else if (msg_type === 'status') {
+ if (content.execution_state === 'busy') {
IPython.kernel_status_widget.status_busy();
- } else if (content.execution_state === "idle") {
+ } else if (content.execution_state === 'idle') {
IPython.kernel_status_widget.status_idle();
+ } else if (content.execution_state === 'dead') {
+ this.handle_status_dead();
};
}
};
+ Notebook.prototype.handle_status_dead = function () {
+ var that = this;
+ this.kernel.stop_channels();
+ var dialog = $('<div/>');
+ dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
+ $(document).append(dialog);
+ dialog.dialog({
+ resizable: false,
+ modal: true,
+ title: "Dead kernel",
+ buttons : {
+ "Yes": function () {
+ that.start_kernel();
+ $(this).dialog('close');
+ },
+ "No": function () {
+ $(this).dialog('close');
+ }
+ }
+ });
+ };
+
+
Notebook.prototype.handle_output = function (cell, msg_type, content) {
var json = {};
json.output_type = msg_type;
@@ -589,12 +627,6 @@ var IPython = (function (IPython) {
return json;
};
- Notebook.prototype.kernel_started = function () {
- console.log("Kernel started: ", this.kernel.kernel_id);
- this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
- this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
- };
-
Notebook.prototype.execute_selected_cell = function (options) {
// add_new: should a new cell be added if we are at the end of the nb
2  IPython/frontend/html/notebook/static/js/panelsection.js
View
@@ -198,7 +198,7 @@ var IPython = (function (IPython) {
KernelSection.prototype.bind_events = function () {
PanelSection.prototype.bind_events.apply(this);
this.content.find('#restart_kernel').click(function () {
- IPython.notebook.kernel.restart();
+ IPython.notebook.restart_kernel();
});
this.content.find('#int_kernel').click(function () {
IPython.notebook.kernel.interrupt();
Please sign in to comment.
Something went wrong with that request. Please try again.