Skip to content
This repository
Browse code

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
@@ -4,20 +4,22 @@
4 4 # Imports
5 5 #-----------------------------------------------------------------------------
6 6
7   -import json
8   -import logging
9   -import os
10   -import urllib
11 7
12 8 from tornado import web
13 9 from tornado import websocket
14 10
  11 +from zmq.eventloop import ioloop
  12 +from zmq.utils import jsonapi
  13 +
  14 +from IPython.zmq.session import Session
  15 +
15 16 try:
16 17 from docutils.core import publish_string
17 18 except ImportError:
18 19 publish_string = None
19 20
20 21
  22 +
21 23 #-----------------------------------------------------------------------------
22 24 # Top-level handlers
23 25 #-----------------------------------------------------------------------------
@@ -52,15 +54,15 @@ def get(self, notebook_id):
52 54 class MainKernelHandler(web.RequestHandler):
53 55
54 56 def get(self):
55   - rkm = self.application.routing_kernel_manager
56   - self.finish(json.dumps(rkm.kernel_ids))
  57 + km = self.application.kernel_manager
  58 + self.finish(jsonapi.dumps(km.kernel_ids))
57 59
58 60 def post(self):
59   - rkm = self.application.routing_kernel_manager
  61 + km = self.application.kernel_manager
60 62 notebook_id = self.get_argument('notebook', default=None)
61   - kernel_id = rkm.start_kernel(notebook_id)
  63 + kernel_id = km.start_kernel(notebook_id)
62 64 self.set_header('Location', '/'+kernel_id)
63   - self.finish(json.dumps(kernel_id))
  65 + self.finish(jsonapi.dumps(kernel_id))
64 66
65 67
66 68 class KernelHandler(web.RequestHandler):
@@ -68,8 +70,8 @@ class KernelHandler(web.RequestHandler):
68 70 SUPPORTED_METHODS = ('DELETE')
69 71
70 72 def delete(self, kernel_id):
71   - rkm = self.application.routing_kernel_manager
72   - rkm.kill_kernel(kernel_id)
  73 + km = self.application.kernel_manager
  74 + km.kill_kernel(kernel_id)
73 75 self.set_status(204)
74 76 self.finish()
75 77
@@ -77,31 +79,124 @@ def delete(self, kernel_id):
77 79 class KernelActionHandler(web.RequestHandler):
78 80
79 81 def post(self, kernel_id, action):
80   - rkm = self.application.routing_kernel_manager
  82 + km = self.application.kernel_manager
81 83 if action == 'interrupt':
82   - rkm.interrupt_kernel(kernel_id)
  84 + km.interrupt_kernel(kernel_id)
83 85 self.set_status(204)
84 86 if action == 'restart':
85   - new_kernel_id = rkm.restart_kernel(kernel_id)
86   - self.write(json.dumps(new_kernel_id))
  87 + new_kernel_id = km.restart_kernel(kernel_id)
  88 + self.write(jsonapi.dumps(new_kernel_id))
87 89 self.finish()
88 90
89 91
90 92 class ZMQStreamHandler(websocket.WebSocketHandler):
91 93
92   - def initialize(self, stream_name):
93   - self.stream_name = stream_name
  94 + def _reserialize_reply(self, msg_list):
  95 + """Reserialize a reply message using JSON.
  96 +
  97 + This takes the msg list from the ZMQ socket, unserializes it using
  98 + self.session and then serializes the result using JSON. This method
  99 + should be used by self._on_zmq_reply to build messages that can
  100 + be sent back to the browser.
  101 + """
  102 + idents, msg_list = self.session.feed_identities(msg_list)
  103 + msg = self.session.unserialize(msg_list)
  104 + msg['header'].pop('date')
  105 + msg.pop('buffers')
  106 + return jsonapi.dumps(msg)
  107 +
  108 +
  109 +class IOPubHandler(ZMQStreamHandler):
  110 +
  111 + def initialize(self, *args, **kwargs):
  112 + self._kernel_alive = True
  113 + self._beating = False
  114 +
  115 + def open(self, kernel_id):
  116 + km = self.application.kernel_manager
  117 + self.kernel_id = kernel_id
  118 + self.session = Session()
  119 + self.time_to_dead = km.time_to_dead
  120 + self.iopub_stream = km.create_iopub_stream(kernel_id)
  121 + self.hb_stream = km.create_hb_stream(kernel_id)
  122 + self.iopub_stream.on_recv(self._on_zmq_reply)
  123 + self.start_hb(self.kernel_died)
  124 +
  125 + def _on_zmq_reply(self, msg_list):
  126 + msg = self._reserialize_reply(msg_list)
  127 + self.write_message(msg)
  128 +
  129 + def on_close(self):
  130 + self.stop_hb()
  131 + self.iopub_stream.close()
  132 + self.hb_stream.close()
  133 +
  134 + def start_hb(self, callback):
  135 + """Start the heartbeating and call the callback if the kernel dies."""
  136 + if not self._beating:
  137 + self._kernel_alive = True
  138 +
  139 + def ping_or_dead():
  140 + if self._kernel_alive:
  141 + self._kernel_alive = False
  142 + self.hb_stream.send(b'ping')
  143 + else:
  144 + try:
  145 + callback()
  146 + except:
  147 + pass
  148 + finally:
  149 + self._hb_periodic_callback.stop()
  150 +
  151 + def beat_received(msg):
  152 + self._kernel_alive = True
  153 +
  154 + self.hb_stream.on_recv(beat_received)
  155 + self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
  156 + self._hb_periodic_callback.start()
  157 + self._beating= True
  158 +
  159 + def stop_hb(self):
  160 + """Stop the heartbeating and cancel all related callbacks."""
  161 + if self._beating:
  162 + self._hb_periodic_callback.stop()
  163 + if not self.hb_stream.closed():
  164 + self.hb_stream.on_recv(None)
  165 +
  166 + def kernel_died(self):
  167 + self.write_message(
  168 + {'header': {'msg_type': 'status'},
  169 + 'parent_header': {},
  170 + 'content': {'execution_state':'dead'}
  171 + }
  172 + )
  173 + self.on_close()
  174 +
  175 +
  176 +class ShellHandler(ZMQStreamHandler):
  177 +
  178 + def initialize(self, *args, **kwargs):
  179 + pass
94 180
95 181 def open(self, kernel_id):
96   - rkm = self.application.routing_kernel_manager
97   - self.router = rkm.get_router(kernel_id, self.stream_name)
98   - self.client_id = self.router.register_client(self)
  182 + km = self.application.kernel_manager
  183 + self.max_msg_size = km.max_msg_size
  184 + self.kernel_id = kernel_id
  185 + self.session = Session()
  186 + self.shell_stream = self.application.kernel_manager.create_shell_stream(kernel_id)
  187 + self.shell_stream.on_recv(self._on_zmq_reply)
  188 +
  189 + def _on_zmq_reply(self, msg_list):
  190 + msg = self._reserialize_reply(msg_list)
  191 + self.write_message(msg)
99 192
100 193 def on_message(self, msg):
101   - self.router.forward_msg(self.client_id, msg)
  194 + if len(msg) < self.max_msg_size:
  195 + msg = jsonapi.loads(msg)
  196 + self.session.send(self.shell_stream, msg)
102 197
103 198 def on_close(self):
104   - self.router.unregister_client(self.client_id)
  199 + self.shell_stream.close()
105 200
106 201
107 202 #-----------------------------------------------------------------------------
@@ -113,7 +208,7 @@ class NotebookRootHandler(web.RequestHandler):
113 208 def get(self):
114 209 nbm = self.application.notebook_manager
115 210 files = nbm.list_notebooks()
116   - self.finish(json.dumps(files))
  211 + self.finish(jsonapi.dumps(files))
117 212
118 213 def post(self):
119 214 nbm = self.application.notebook_manager
@@ -125,7 +220,7 @@ def post(self):
125 220 else:
126 221 notebook_id = nbm.new_notebook()
127 222 self.set_header('Location', '/'+notebook_id)
128   - self.finish(json.dumps(notebook_id))
  223 + self.finish(jsonapi.dumps(notebook_id))
129 224
130 225
131 226 class NotebookHandler(web.RequestHandler):
@@ -175,11 +270,10 @@ def post(self):
175 270 body = self.request.body.strip()
176 271 source = body
177 272 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
178   - print template_path
179 273 defaults = {'file_insertion_enabled': 0,
180 274 'raw_enabled': 0,
181 275 '_disable_config': 1,
182   - 'stylesheet_path': 0,
  276 + 'stylesheet_path': 0
183 277 # 'template': template_path
184 278 }
185 279 try:
134 IPython/frontend/html/notebook/kernelmanager.py
@@ -16,14 +16,13 @@
16 16 import uuid
17 17
18 18 import zmq
  19 +from zmq.eventloop.zmqstream import ZMQStream
19 20
20 21 from tornado import web
21 22
22   -from .routers import IOPubStreamRouter, ShellStreamRouter
23   -
24 23 from IPython.config.configurable import LoggingConfigurable
25 24 from IPython.zmq.ipkernel import launch_kernel
26   -from IPython.utils.traitlets import Instance, Dict, List, Unicode
  25 +from IPython.utils.traitlets import Instance, Dict, List, Unicode, Float, Int
27 26
28 27 #-----------------------------------------------------------------------------
29 28 # Classes
@@ -110,6 +109,7 @@ def interrupt_kernel(self, kernel_id):
110 109 else:
111 110 kernel_process.send_signal(signal.SIGINT)
112 111
  112 +
113 113 def signal_kernel(self, kernel_id, signum):
114 114 """ Sends a signal to the kernel by its uuid.
115 115
@@ -182,34 +182,50 @@ def get_kernel_ip(self, kernel_id):
182 182 else:
183 183 raise KeyError("Kernel with id not found: %s" % kernel_id)
184 184
185   - def create_session_manager(self, kernel_id):
186   - """Create a new session manager for a kernel by its uuid."""
187   - from sessionmanager import SessionManager
188   - return SessionManager(
189   - kernel_id=kernel_id, kernel_manager=self,
190   - config=self.config, context=self.context, log=self.log
191   - )
  185 + def create_connected_stream(self, ip, port, socket_type):
  186 + sock = self.context.socket(socket_type)
  187 + addr = "tcp://%s:%i" % (ip, port)
  188 + self.log.info("Connecting to: %s" % addr)
  189 + sock.connect(addr)
  190 + return ZMQStream(sock)
  191 +
  192 + def create_iopub_stream(self, kernel_id):
  193 + ip = self.get_kernel_ip(kernel_id)
  194 + ports = self.get_kernel_ports(kernel_id)
  195 + iopub_stream = self.create_connected_stream(ip, ports['iopub_port'], zmq.SUB)
  196 + iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
  197 + return iopub_stream
192 198
  199 + def create_shell_stream(self, kernel_id):
  200 + ip = self.get_kernel_ip(kernel_id)
  201 + ports = self.get_kernel_ports(kernel_id)
  202 + shell_stream = self.create_connected_stream(ip, ports['shell_port'], zmq.XREQ)
  203 + return shell_stream
193 204
194   -class RoutingKernelManager(LoggingConfigurable):
195   - """A KernelManager that handles WebSocket routing and HTTP error handling"""
  205 + def create_hb_stream(self, kernel_id):
  206 + ip = self.get_kernel_ip(kernel_id)
  207 + ports = self.get_kernel_ports(kernel_id)
  208 + hb_stream = self.create_connected_stream(ip, ports['hb_port'], zmq.REQ)
  209 + return hb_stream
  210 +
  211 +
  212 +class MappingKernelManager(KernelManager):
  213 + """A KernelManager that handles notebok mapping and HTTP error handling"""
196 214
197 215 kernel_argv = List(Unicode)
198 216 kernel_manager = Instance(KernelManager)
  217 + time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
  218 + max_msg_size = Int(65536, config=True, help="""
  219 + The max raw message size accepted from the browser
  220 + over a WebSocket connection.
  221 + """)
199 222
200   - _routers = Dict()
201   - _session_dict = Dict()
202 223 _notebook_mapping = Dict()
203 224
204 225 #-------------------------------------------------------------------------
205 226 # Methods for managing kernels and sessions
206 227 #-------------------------------------------------------------------------
207 228
208   - @property
209   - def kernel_ids(self):
210   - """List the kernel ids."""
211   - return self.kernel_manager.kernel_ids
212   -
213 229 def kernel_for_notebook(self, notebook_id):
214 230 """Return the kernel_id for a notebook_id or None."""
215 231 return self._notebook_mapping.get(notebook_id)
@@ -234,7 +250,7 @@ def delete_mapping_for_kernel(self, kernel_id):
234 250 del self._notebook_mapping[notebook_id]
235 251
236 252 def start_kernel(self, notebook_id=None):
237   - """Start a kernel an return its kernel_id.
  253 + """Start a kernel for a notebok an return its kernel_id.
238 254
239 255 Parameters
240 256 ----------
@@ -243,108 +259,48 @@ def start_kernel(self, notebook_id=None):
243 259 is not None, this kernel will be persistent whenever the notebook
244 260 requests a kernel.
245 261 """
246   - self.log.info
247 262 kernel_id = self.kernel_for_notebook(notebook_id)
248 263 if kernel_id is None:
249 264 kwargs = dict()
250 265 kwargs['extra_arguments'] = self.kernel_argv
251   - kernel_id = self.kernel_manager.start_kernel(**kwargs)
  266 + kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
252 267 self.set_kernel_for_notebook(notebook_id, kernel_id)
253 268 self.log.info("Kernel started: %s" % kernel_id)
254 269 self.log.debug("Kernel args: %r" % kwargs)
255   - self.start_session_manager(kernel_id)
256 270 else:
257 271 self.log.info("Using existing kernel: %s" % kernel_id)
258 272 return kernel_id
259 273
260   - def start_session_manager(self, kernel_id):
261   - """Start the ZMQ sockets (a "session") to connect to a kernel."""
262   - sm = self.kernel_manager.create_session_manager(kernel_id)
263   - self._session_dict[kernel_id] = sm
264   - iopub_stream = sm.get_iopub_stream()
265   - shell_stream = sm.get_shell_stream()
266   - iopub_router = IOPubStreamRouter(
267   - zmq_stream=iopub_stream, session=sm.session, config=self.config
268   - )
269   - shell_router = ShellStreamRouter(
270   - zmq_stream=shell_stream, session=sm.session, config=self.config
271   - )
272   - self.set_router(kernel_id, 'iopub', iopub_router)
273   - self.set_router(kernel_id, 'shell', shell_router)
274   -
275 274 def kill_kernel(self, kernel_id):
276 275 """Kill a kernel and remove its notebook association."""
277   - if kernel_id not in self.kernel_manager:
  276 + if kernel_id not in self:
278 277 raise web.HTTPError(404)
279   - try:
280   - sm = self._session_dict.pop(kernel_id)
281   - except KeyError:
282   - raise web.HTTPError(404)
283   - sm.stop()
284   - self.kernel_manager.kill_kernel(kernel_id)
  278 + super(MappingKernelManager, self).kill_kernel(kernel_id)
285 279 self.delete_mapping_for_kernel(kernel_id)
286 280 self.log.info("Kernel killed: %s" % kernel_id)
287 281
288 282 def interrupt_kernel(self, kernel_id):
289 283 """Interrupt a kernel."""
290   - if kernel_id not in self.kernel_manager:
  284 + if kernel_id not in self:
291 285 raise web.HTTPError(404)
292   - self.kernel_manager.interrupt_kernel(kernel_id)
293   - self.log.debug("Kernel interrupted: %s" % kernel_id)
  286 + super(MappingKernelManager, self).interrupt_kernel(kernel_id)
  287 + self.log.info("Kernel interrupted: %s" % kernel_id)
294 288
295 289 def restart_kernel(self, kernel_id):
296 290 """Restart a kernel while keeping clients connected."""
297   - if kernel_id not in self.kernel_manager:
  291 + if kernel_id not in self:
298 292 raise web.HTTPError(404)
299 293
300   - # Get the notebook_id to preserve the kernel/notebook association
  294 + # Get the notebook_id to preserve the kernel/notebook association.
301 295 notebook_id = self.notebook_for_kernel(kernel_id)
302 296 # Create the new kernel first so we can move the clients over.
303 297 new_kernel_id = self.start_kernel()
304   -
305   - # Copy the clients over to the new routers.
306   - old_iopub_router = self.get_router(kernel_id, 'iopub')
307   - old_shell_router = self.get_router(kernel_id, 'shell')
308   - new_iopub_router = self.get_router(new_kernel_id, 'iopub')
309   - new_shell_router = self.get_router(new_kernel_id, 'shell')
310   - new_iopub_router.copy_clients(old_iopub_router)
311   - new_shell_router.copy_clients(old_shell_router)
312   -
313   - # Shut down the old routers
314   - old_shell_router.close()
315   - old_iopub_router.close()
316   - self.delete_router(kernel_id, 'shell')
317   - self.delete_router(kernel_id, 'iopub')
318   - del old_shell_router
319   - del old_iopub_router
320   -
321   - # Now shutdown the old session and the kernel.
322   - # TODO: This causes a hard crash in ZMQStream.close, which sets
323   - # self.socket to None to hastily. We will need to fix this in PyZMQ
324   - # itself. For now, we just leave the old kernel running :(
325   - # Maybe this is fixed now, but nothing was changed really.
  298 + # Now kill the old kernel.
326 299 self.kill_kernel(kernel_id)
327   -
328 300 # Now save the new kernel/notebook association. We have to save it
329 301 # after the old kernel is killed as that will delete the mapping.
330 302 self.set_kernel_for_notebook(notebook_id, new_kernel_id)
331   -
332 303 self.log.debug("Kernel restarted: %s" % new_kernel_id)
333 304 return new_kernel_id
334 305
335   - def get_router(self, kernel_id, stream_name):
336   - """Return the router for a given kernel_id and stream name."""
337   - router = self._routers[(kernel_id, stream_name)]
338   - return router
339   -
340   - def set_router(self, kernel_id, stream_name, router):
341   - """Set the router for a given kernel_id and stream_name."""
342   - self._routers[(kernel_id, stream_name)] = router
343   -
344   - def delete_router(self, kernel_id, stream_name):
345   - """Delete a router for a kernel_id and stream_name."""
346   - try:
347   - del self._routers[(kernel_id, stream_name)]
348   - except KeyError:
349   - pass
350 306
28 IPython/frontend/html/notebook/notebookapp.py
@@ -27,12 +27,11 @@
27 27 from tornado import httpserver
28 28 from tornado import web
29 29
30   -from .kernelmanager import KernelManager, RoutingKernelManager
31   -from .sessionmanager import SessionManager
  30 +from .kernelmanager import MappingKernelManager
32 31 from .handlers import (
33 32 NBBrowserHandler, NewHandler, NamedNotebookHandler,
34   - MainKernelHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
35   - NotebookRootHandler, NotebookHandler, RSTHandler
  33 + MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
  34 + ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
36 35 )
37 36 from .notebookmanager import NotebookManager
38 37
@@ -45,7 +44,7 @@
45 44 aliases as ipkernel_aliases,
46 45 IPKernelApp
47 46 )
48   -from IPython.utils.traitlets import Dict, Unicode, Int, Any, List, Enum
  47 +from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum
49 48
50 49 #-----------------------------------------------------------------------------
51 50 # Module globals
@@ -71,7 +70,7 @@
71 70
72 71 class NotebookWebApplication(web.Application):
73 72
74   - def __init__(self, routing_kernel_manager, notebook_manager, log):
  73 + def __init__(self, kernel_manager, notebook_manager, log):
75 74 handlers = [
76 75 (r"/", NBBrowserHandler),
77 76 (r"/new", NewHandler),
@@ -79,8 +78,8 @@ def __init__(self, routing_kernel_manager, notebook_manager, log):
79 78 (r"/kernels", MainKernelHandler),
80 79 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
81 80 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
82   - (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
83   - (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
  81 + (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
  82 + (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
84 83 (r"/notebooks", NotebookRootHandler),
85 84 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
86 85 (r"/rstservice/render", RSTHandler)
@@ -91,7 +90,7 @@ def __init__(self, routing_kernel_manager, notebook_manager, log):
91 90 )
92 91 web.Application.__init__(self, handlers, **settings)
93 92
94   - self.routing_kernel_manager = routing_kernel_manager
  93 + self.kernel_manager = kernel_manager
95 94 self.log = log
96 95 self.notebook_manager = notebook_manager
97 96
@@ -114,7 +113,6 @@ def __init__(self, routing_kernel_manager, notebook_manager, log):
114 113 'port': 'IPythonNotebookApp.port',
115 114 'keyfile': 'IPythonNotebookApp.keyfile',
116 115 'certfile': 'IPythonNotebookApp.certfile',
117   - 'colors': 'ZMQInteractiveShell.colors',
118 116 'notebook-dir': 'NotebookManager.notebook_dir'
119 117 })
120 118
@@ -136,8 +134,7 @@ class IPythonNotebookApp(BaseIPythonApplication):
136 134 examples = _examples
137 135
138 136 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
139   - RoutingKernelManager, NotebookManager,
140   - KernelManager, SessionManager]
  137 + MappingKernelManager, NotebookManager]
141 138 flags = Dict(flags)
142 139 aliases = Dict(aliases)
143 140
@@ -187,9 +184,8 @@ def init_configurables(self):
187 184 signal.signal(signal.SIGINT, signal.SIG_DFL)
188 185
189 186 # Create a KernelManager and start a kernel.
190   - self.kernel_manager = KernelManager(config=self.config, log=self.log)
191   - self.routing_kernel_manager = RoutingKernelManager(config=self.config, log=self.log,
192   - kernel_manager=self.kernel_manager, kernel_argv=self.kernel_argv
  187 + self.kernel_manager = MappingKernelManager(
  188 + config=self.config, log=self.log, kernel_argv=self.kernel_argv
193 189 )
194 190 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
195 191
@@ -204,7 +200,7 @@ def initialize(self, argv=None):
204 200 super(IPythonNotebookApp, self).initialize(argv)
205 201 self.init_configurables()
206 202 self.web_app = NotebookWebApplication(
207   - self.routing_kernel_manager, self.notebook_manager, self.log
  203 + self.kernel_manager, self.notebook_manager, self.log
208 204 )
209 205 if self.certfile:
210 206 ssl_options = dict(certfile=self.certfile)
125 IPython/frontend/html/notebook/routers.py
... ... @@ -1,125 +0,0 @@
1   -"""Routers that connect WebSockets to ZMQ sockets."""
2   -
3   -#-----------------------------------------------------------------------------
4   -# Copyright (C) 2011 The IPython Development Team
5   -#
6   -# Distributed under the terms of the BSD License. The full license is in
7   -# the file COPYING.txt, distributed as part of this software.
8   -#-----------------------------------------------------------------------------
9   -
10   -#-----------------------------------------------------------------------------
11   -# Imports
12   -#-----------------------------------------------------------------------------
13   -
14   -import uuid
15   -from Queue import Queue
16   -import json
17   -
18   -from IPython.config.configurable import Configurable
19   -from IPython.utils.traitlets import Instance, Int, Dict
20   -
21   -#-----------------------------------------------------------------------------
22   -# Classes
23   -#-----------------------------------------------------------------------------
24   -
25   -class ZMQStreamRouter(Configurable):
26   -
27   - zmq_stream = Instance('zmq.eventloop.zmqstream.ZMQStream')
28   - session = Instance('IPython.zmq.session.Session')
29   - max_msg_size = Int(2048, config=True, help="""
30   - The max raw message size accepted from the browser
31   - over a WebSocket connection.
32   - """)
33   -
34   - _clients = Dict()
35   -
36   - def __init__(self, **kwargs):
37   - super(ZMQStreamRouter,self).__init__(**kwargs)
38   - self.zmq_stream.on_recv(self._on_zmq_reply)
39   -
40   - def __del__(self):
41   - self.close()
42   -
43   - def close(self):
44   - """Disable the routing actions of this router."""
45   - self._clients = {}
46   - self.zmq_stream.on_recv(None)
47   -
48   - def register_client(self, client):
49   - """Register a client, returning a client uuid."""
50   - client_id = unicode(uuid.uuid4())
51   - self._clients[client_id] = client
52   - return client_id
53   -
54   - def unregister_client(self, client_id):
55   - """Unregister a client by its client uuid."""
56   - del self._clients[client_id]
57   -
58   - def copy_clients(self, router):
59   - """Copy the clients of another router to this one.
60   -
61   - This is used to enable the backend zeromq stream to disconnect
62   - and reconnect while the WebSocket connections to browsers
63   - remain, such as when a kernel is restarted.
64   - """
65   - for client_id, client in router._clients.items():
66   - client.router = self
67   - self._clients[client_id] = client
68   -
69   - def forward_msg(self, client_id, msg):
70   - """Forward a msg to a client by its id.
71   -
72   - The default implementation of this will fail silently if a message
73   - arrives on a socket that doesn't support it. This method should
74   - use max_msg_size to check and silently discard message that are too
75   - long."""
76   - pass
77   -
78   - def _on_zmq_reply(self, msg_list):
79   - """Handle a message the ZMQ stream sends to the router.
80   -
81   - Usually, this is where the return message will be written to
82   - clients that need it using client.write_message().
83   - """
84   - pass
85   -
86   - def _reserialize_reply(self, msg_list):
87   - """Reserialize a reply message using JSON.
88   -
89   - This takes the msg list from the ZMQ socket, unserializes it using
90   - self.session and then serializes the result using JSON. This method
91   - should be used by self._on_zmq_reply to build messages that can
92   - be sent back to the browser.
93   - """
94   - idents, msg_list = self.session.feed_identities(msg_list)
95   - msg = self.session.unserialize(msg_list)
96   - msg['header'].pop('date')
97   - msg.pop('buffers')
98   - return json.dumps(msg)
99   -
100   -
101   -class IOPubStreamRouter(ZMQStreamRouter):
102   -
103   - def _on_zmq_reply(self, msg_list):
104   - msg = self._reserialize_reply(msg_list)
105   - for client_id, client in self._clients.items():
106   - client.write_message(msg)
107   -
108   -
109   -class ShellStreamRouter(ZMQStreamRouter):
110   -
111   - _request_queue = Instance(Queue,(),{})
112   -
113   - def _on_zmq_reply(self, msg_list):
114   - msg = self._reserialize_reply(msg_list)
115   - client_id = self._request_queue.get(block=False)
116   - client = self._clients.get(client_id)
117   - if client is not None:
118   - client.write_message(msg)
119   -
120   - def forward_msg(self, client_id, msg):
121   - if len(msg) < self.max_msg_size:
122   - msg = json.loads(msg)
123   - self._request_queue.put(client_id)
124   - self.session.send(self.zmq_stream, msg)
125   -
90 IPython/frontend/html/notebook/sessionmanager.py
... ... @@ -1,90 +0,0 @@
1   -"""A manager for session and channels for a single kernel."""
2   -
3   -#-----------------------------------------------------------------------------
4   -# Copyright (C) 2011 The IPython Development Team
5   -#
6   -# Distributed under the terms of the BSD License. The full license is in
7   -# the file COPYING.txt, distributed as part of this software.
8   -#-----------------------------------------------------------------------------
9   -
10   -#-----------------------------------------------------------------------------
11   -# Imports
12   -#-----------------------------------------------------------------------------
13   -
14   -import zmq
15   -from zmq.eventloop.zmqstream import ZMQStream
16   -
17   -from IPython.utils.traitlets import Instance, Dict, CBytes, Bool
18   -from IPython.zmq.session import SessionFactory
19   -
20   -
21   -class SessionManagerRunningError(Exception):
22   - pass
23   -
24   -#-----------------------------------------------------------------------------
25   -# Classes
26   -#-----------------------------------------------------------------------------
27   -
28   -
29   -class SessionManager(SessionFactory):
30   - """Manages a session for a kernel.
31   -
32   - The object manages a variety of things for a connection session to
33   - a running kernel:
34   -
35   - * The set of channels or connected ZMQ streams to the kernel.
36   - * An IPython.zmq.session.Session object that manages send/recv logic
37   - for those channels.
38   - """
39   -
40   - kernel_manager = Instance('IPython.frontend.html.notebook.kernelmanager.KernelManager')
41   - kernel_id = CBytes(b'')
42   - _session_streams = Dict()
43   - _running = Bool(False)
44   -
45   - def __init__(self, **kwargs):
46   - kernel_id = kwargs.pop('kernel_id')
47   - super(SessionManager, self).__init__(**kwargs)
48   - self.kernel_id = kernel_id
49   - self.start()
50   -
51   - def __del__(self):
52   - self.stop()
53   -
54   - def start(self):
55   - if not self._running:
56   - ports = self.kernel_manager.get_kernel_ports(self.kernel_id)
57   - iopub_stream = self.create_connected_stream(ports['iopub_port'], zmq.SUB)
58   - iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
59   - shell_stream = self.create_connected_stream(ports['shell_port'], zmq.XREQ)
60   - self._session_streams = dict(
61   - iopub_stream = iopub_stream,
62   - shell_stream = shell_stream
63   - )
64   - self._running = True
65   - else:
66   - raise SessionManagerRunningError(
67   - 'Session manager is already running, call stop() before start()'
68   - )
69   -
70   - def stop(self):
71   - if self._running:
72   - for name, stream in self._session_streams.items():
73   - stream.close()
74   - self._session_streams = {}
75   - self._running = False
76   -
77   - def create_connected_stream(self, port, socket_type):
78   - sock = self.context.socket(socket_type)
79   - addr = "tcp://%s:%i" % (self.kernel_manager.get_kernel_ip(self.kernel_id), port)
80   - self.log.info("Connecting to: %s" % addr)
81   - sock.connect(addr)
82   - return ZMQStream(sock)
83   -
84   - def get_iopub_stream(self):
85   - return self._session_streams['iopub_stream']
86   -
87   - def get_shell_stream(self):
88   - return self._session_streams['shell_stream']
89   -
90   -
85 IPython/frontend/html/notebook/static/js/kernel.js
@@ -11,6 +11,9 @@ var IPython = (function (IPython) {
11 11 this.kernel_id = null;
12 12 this.base_url = "/kernels";
13 13 this.kernel_url = null;
  14 + this.shell_channel = null;
  15 + this.iopub_channel = null;
  16 + this.running = false;
14 17 };
15 18
16 19
@@ -28,33 +31,65 @@ var IPython = (function (IPython) {
28 31 return msg;
29 32 }
30 33
31   - Kernel.prototype.start_kernel = function (notebook_id, callback) {
  34 + Kernel.prototype.start = function (notebook_id, callback) {
32 35 var that = this;
33   - var qs = $.param({notebook:notebook_id});
34   - $.post(this.base_url + '?' + qs,
35   - function (kernel_id) {
36   - that._handle_start_kernel(kernel_id, callback);
37   - },
38   - 'json'
39   - );
  36 + if (!this.running) {
  37 + var qs = $.param({notebook:notebook_id});
  38 + $.post(this.base_url + '?' + qs,
  39 + function (kernel_id) {
  40 + that._handle_start_kernel(kernel_id, callback);
  41 + },
  42 + 'json'
  43 + );
  44 + };
  45 + };
  46 +
  47 +
  48 + Kernel.prototype.restart = function (callback) {
  49 + IPython.kernel_status_widget.status_restarting();
  50 + var url = this.kernel_url + "/restart";
  51 + var that = this;
  52 + if (this.running) {
  53 + this.stop_channels();
  54 + $.post(url,
  55 + function (kernel_id) {
  56 + that._handle_start_kernel(kernel_id, callback);
  57 + },
  58 + 'json'
  59 + );
  60 + };
40 61 };
41 62
42 63
43 64 Kernel.prototype._handle_start_kernel = function (kernel_id, callback) {
  65 + this.running = true;
44 66 this.kernel_id = kernel_id;
45 67 this.kernel_url = this.base_url + "/" + this.kernel_id;
46   - this._start_channels();
  68 + this.start_channels();
47 69 callback();
  70 + IPython.kernel_status_widget.status_idle();
48 71 };
49 72
50 73
51   - Kernel.prototype._start_channels = function () {
  74 + Kernel.prototype.start_channels = function () {
  75 + this.stop_channels();
52 76 var ws_url = "ws://127.0.0.1:8888" + this.kernel_url;
53 77 this.shell_channel = new WebSocket(ws_url + "/shell");
54 78 this.iopub_channel = new WebSocket(ws_url + "/iopub");
55   - }
  79 + };
56 80
57 81
  82 + Kernel.prototype.stop_channels = function () {
  83 + if (this.shell_channel !== null) {
  84 + this.shell_channel.close();
  85 + this.shell_channel = null;
  86 + };
  87 + if (this.iopub_channel !== null) {
  88 + this.iopub_channel.close();
  89 + this.iopub_channel = null;
  90 + };
  91 + };
  92 +
58 93 Kernel.prototype.execute = function (code) {
59 94 var content = {
60 95 code : code,
@@ -81,29 +116,21 @@ var IPython = (function (IPython) {
81 116
82 117
83 118 Kernel.prototype.interrupt = function () {
84   - $.post(this.kernel_url + "/interrupt");
85   - };
86   -
87   -
88   - Kernel.prototype.restart = function () {
89   - IPython.kernel_status_widget.status_restarting();
90   - var url = this.kernel_url + "/restart"
91   - var that = this;
92   - $.post(url, function (kernel_id) {
93   - console.log("Kernel restarted: " + kernel_id);
94   - that.kernel_id = kernel_id;
95   - that.kernel_url = that.base_url + "/" + that.kernel_id;
96   - IPython.kernel_status_widget.status_idle();
97   - }, 'json');
  119 + if (this.running) {
  120 + $.post(this.kernel_url + "/interrupt");
  121 + };
98 122 };
99 123
100 124
101 125 Kernel.prototype.kill = function () {
102   - var settings = {
103   - cache : false,
104   - type : "DELETE",
  126 + if (this.running) {
  127 + this.running = false;
  128 + var settings = {
  129 + cache : false,
  130 + type : "DELETE",
  131 + };
  132 + $.ajax(this.kernel_url, settings);
105 133 };
106   - $.ajax(this.kernel_url, settings);
107 134 };
108 135
109 136 IPython.Kernel = Kernel;
52 IPython/frontend/html/notebook/static/js/notebook.js
@@ -482,7 +482,20 @@ var IPython = (function (IPython) {
482 482 Notebook.prototype.start_kernel = function () {
483 483 this.kernel = new IPython.Kernel();
484 484 var notebook_id = IPython.save_widget.get_notebook_id();
485   - this.kernel.start_kernel(notebook_id, $.proxy(this.kernel_started, this));
  485 + this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
  486 + };
  487 +
  488 +
  489 + Notebook.prototype.restart_kernel = function () {
  490 + var notebook_id = IPython.save_widget.get_notebook_id();
  491 + this.kernel.restart($.proxy(this.kernel_started, this));
  492 + };
  493 +
  494 +
  495 + Notebook.prototype.kernel_started = function () {
  496 + console.log("Kernel started: ", this.kernel.kernel_id);
  497 + this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
  498 + this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
486 499 };
487 500
488 501
@@ -528,16 +541,41 @@ var IPython = (function (IPython) {
528 541 var output_types = ['stream','display_data','pyout','pyerr'];
529 542 if (output_types.indexOf(msg_type) >= 0) {
530 543 this.handle_output(cell, msg_type, content);
531   - } else if (msg_type === "status") {
532   - if (content.execution_state === "busy") {
  544 + } else if (msg_type === 'status') {
  545 + if (content.execution_state === 'busy') {
533 546 IPython.kernel_status_widget.status_busy();
534   - } else if (content.execution_state === "idle") {
  547 + } else if (content.execution_state === 'idle') {
535 548 IPython.kernel_status_widget.status_idle();
  549 + } else if (content.execution_state === 'dead') {
  550 + this.handle_status_dead();
536 551 };
537 552 }
538 553 };
539 554
540 555
  556 + Notebook.prototype.handle_status_dead = function () {
  557 + var that = this;
  558 + this.kernel.stop_channels();
  559 + var dialog = $('<div/>');
  560 + 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.');
  561 + $(document).append(dialog);
  562 + dialog.dialog({
  563 + resizable: false,
  564 + modal: true,
  565 + title: "Dead kernel",
  566 + buttons : {
  567 + "Yes": function () {
  568 + that.start_kernel();
  569 + $(this).dialog('close');
  570 + },
  571 + "No": function () {
  572 + $(this).dialog('close');
  573 + }
  574 + }
  575 + });
  576 + };
  577 +
  578 +
541 579 Notebook.prototype.handle_output = function (cell, msg_type, content) {
542 580 var json = {};
543 581 json.output_type = msg_type;
@@ -589,12 +627,6 @@ var IPython = (function (IPython) {
589 627 return json;
590 628 };
591 629
592   - Notebook.prototype.kernel_started = function () {
593   - console.log("Kernel started: ", this.kernel.kernel_id);
594   - this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
595   - this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
596   - };
597   -
598 630
599 631 Notebook.prototype.execute_selected_cell = function (options) {
600 632 // 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
@@ -198,7 +198,7 @@ var IPython = (function (IPython) {
198 198 KernelSection.prototype.bind_events = function () {
199 199 PanelSection.prototype.bind_events.apply(this);
200 200 this.content.find('#restart_kernel').click(function () {
201   - IPython.notebook.kernel.restart();
  201 + IPython.notebook.restart_kernel();
202 202 });
203 203 this.content.find('#int_kernel').click(function () {
204 204 IPython.notebook.kernel.interrupt();

0 comments on commit 5bad195

Please sign in to comment.
Something went wrong with that request. Please try again.