diff --git a/IPython/frontend/html/notebook/handlers.py b/IPython/frontend/html/notebook/handlers.py index aa272a4..4560831 100644 --- a/IPython/frontend/html/notebook/handlers.py +++ b/IPython/frontend/html/notebook/handlers.py @@ -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 @@ class NamedNotebookHandler(web.RequestHandler): 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 @@ class KernelHandler(web.RequestHandler): 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 @@ class NotebookRootHandler(web.RequestHandler): 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 @@ class RSTHandler(web.RequestHandler): 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: diff --git a/IPython/frontend/html/notebook/kernelmanager.py b/IPython/frontend/html/notebook/kernelmanager.py index e6735bb..de6796f 100644 --- a/IPython/frontend/html/notebook/kernelmanager.py +++ b/IPython/frontend/html/notebook/kernelmanager.py @@ -16,14 +16,13 @@ import sys 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 @@ class KernelManager(LoggingConfigurable): 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 @@ class KernelManager(LoggingConfigurable): 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 @@ class RoutingKernelManager(LoggingConfigurable): 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 @@ class RoutingKernelManager(LoggingConfigurable): 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 diff --git a/IPython/frontend/html/notebook/notebookapp.py b/IPython/frontend/html/notebook/notebookapp.py index 6ece199..72750d6 100644 --- a/IPython/frontend/html/notebook/notebookapp.py +++ b/IPython/frontend/html/notebook/notebookapp.py @@ -27,12 +27,11 @@ tornado.ioloop = ioloop 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 @@ from IPython.zmq.ipkernel import ( 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 @@ ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces 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 @@ class NotebookWebApplication(web.Application): (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 @@ class NotebookWebApplication(web.Application): ) 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 @@ aliases.update({ '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 @@ class IPythonNotebookApp(BaseIPythonApplication): 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 @@ class IPythonNotebookApp(BaseIPythonApplication): 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) diff --git a/IPython/frontend/html/notebook/routers.py b/IPython/frontend/html/notebook/routers.py deleted file mode 100644 index 7ff8a0c..0000000 --- a/IPython/frontend/html/notebook/routers.py +++ /dev/null @@ -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) - diff --git a/IPython/frontend/html/notebook/sessionmanager.py b/IPython/frontend/html/notebook/sessionmanager.py deleted file mode 100644 index 6d990fa..0000000 --- a/IPython/frontend/html/notebook/sessionmanager.py +++ /dev/null @@ -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'] - - diff --git a/IPython/frontend/html/notebook/static/js/kernel.js b/IPython/frontend/html/notebook/static/js/kernel.js index a5ff274..f7a22d2 100644 --- a/IPython/frontend/html/notebook/static/js/kernel.js +++ b/IPython/frontend/html/notebook/static/js/kernel.js @@ -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; diff --git a/IPython/frontend/html/notebook/static/js/notebook.js b/IPython/frontend/html/notebook/static/js/notebook.js index a7937ce..994a917 100644 --- a/IPython/frontend/html/notebook/static/js/notebook.js +++ b/IPython/frontend/html/notebook/static/js/notebook.js @@ -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 = $('
'); + 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 diff --git a/IPython/frontend/html/notebook/static/js/panelsection.js b/IPython/frontend/html/notebook/static/js/panelsection.js index 2e0df3a..04fea33 100644 --- a/IPython/frontend/html/notebook/static/js/panelsection.js +++ b/IPython/frontend/html/notebook/static/js/panelsection.js @@ -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();