##// END OF EJS Templates
Adding kernel/notebook associations.
Brian E. Granger -
Show More
@@ -13,7 +13,7 b' from tornado import websocket'
13 13
14 14
15 15 #-----------------------------------------------------------------------------
16 # Handlers
16 # Top-level handlers
17 17 #-----------------------------------------------------------------------------
18 18
19 19
@@ -38,25 +38,45 b' class NamedNotebookHandler(web.RequestHandler):'
38 38 self.render('notebook.html', notebook_id=notebook_id)
39 39
40 40
41 class KernelHandler(web.RequestHandler):
41 #-----------------------------------------------------------------------------
42 # Kernel handlers
43 #-----------------------------------------------------------------------------
44
45
46 class MainKernelHandler(web.RequestHandler):
42 47
43 48 def get(self):
44 self.finish(json.dumps(self.application.kernel_ids))
49 rkm = self.application.routing_kernel_manager
50 self.finish(json.dumps(rkm.kernel_ids))
45 51
46 52 def post(self):
47 kernel_id = self.application.start_kernel()
53 rkm = self.application.routing_kernel_manager
54 notebook_id = self.get_argument('notebook', default=None)
55 kernel_id = rkm.start_kernel(notebook_id)
48 56 self.set_header('Location', '/'+kernel_id)
49 57 self.finish(json.dumps(kernel_id))
50 58
51 59
60 class KernelHandler(web.RequestHandler):
61
62 SUPPORTED_METHODS = ('DELETE')
63
64 def delete(self, kernel_id):
65 rkm = self.application.routing_kernel_manager
66 self.kill_kernel(kernel_id)
67 self.set_status(204)
68 self.finish()
69
70
52 71 class KernelActionHandler(web.RequestHandler):
53 72
54 73 def post(self, kernel_id, action):
55 # TODO: figure out a better way of handling RPC style calls.
74 rkm = self.application.routing_kernel_manager
56 75 if action == 'interrupt':
57 self.application.interrupt_kernel(kernel_id)
76 rkm.interrupt_kernel(kernel_id)
77 self.set_status(204)
58 78 if action == 'restart':
59 new_kernel_id = self.application.restart_kernel(kernel_id)
79 new_kernel_id = rkm.restart_kernel(kernel_id)
60 80 self.write(json.dumps(new_kernel_id))
61 81 self.finish()
62 82
@@ -67,7 +87,8 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
67 87 self.stream_name = stream_name
68 88
69 89 def open(self, kernel_id):
70 self.router = self.application.get_router(kernel_id, self.stream_name)
90 rkm = self.application.routing_kernel_manager
91 self.router = rkm.get_router(kernel_id, self.stream_name)
71 92 self.client_id = self.router.register_client(self)
72 93 logging.info("Connection open: %s, %s" % (kernel_id, self.client_id))
73 94
@@ -79,6 +100,10 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
79 100 logging.info("Connection closed: %s" % self.client_id)
80 101
81 102
103 #-----------------------------------------------------------------------------
104 # Notebook web service handlers
105 #-----------------------------------------------------------------------------
106
82 107 class NotebookRootHandler(web.RequestHandler):
83 108
84 109 def get(self):
@@ -17,9 +17,13 b' import uuid'
17 17
18 18 import zmq
19 19
20 from tornado import web
21
22 from .routers import IOPubStreamRouter, ShellStreamRouter
23
20 24 from IPython.config.configurable import LoggingConfigurable
21 25 from IPython.zmq.ipkernel import launch_kernel
22 from IPython.utils.traitlets import Instance, Dict
26 from IPython.utils.traitlets import Instance, Dict, List, Unicode
23 27
24 28 #-----------------------------------------------------------------------------
25 29 # Classes
@@ -55,7 +59,7 b' class KernelManager(LoggingConfigurable):'
55 59
56 60 def start_kernel(self, **kwargs):
57 61 """Start a new kernel."""
58 kernel_id = str(uuid.uuid4())
62 kernel_id = unicode(uuid.uuid4())
59 63 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(**kwargs)
60 64 # Store the information for contacting the kernel. This assumes the kernel is
61 65 # running on localhost.
@@ -186,3 +190,117 b' class KernelManager(LoggingConfigurable):'
186 190 config=self.config, context=self.context, log=self.log
187 191 )
188 192
193
194 class RoutingKernelManager(LoggingConfigurable):
195 """A KernelManager that handles WebSocket routing and HTTP error handling"""
196
197 kernel_argv = List(Unicode)
198 kernel_manager = Instance(KernelManager)
199
200 _routers = Dict()
201 _session_dict = Dict()
202 _notebook_mapping = Dict()
203
204 #-------------------------------------------------------------------------
205 # Methods for managing kernels and sessions
206 #-------------------------------------------------------------------------
207
208 @property
209 def kernel_ids(self):
210 return self.kernel_manager.kernel_ids
211
212 def notebook_for_kernel(self, kernel_id):
213 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
214 if len(notebook_ids) == 1:
215 return notebook_ids[0]
216 else:
217 return None
218
219 def delete_mapping_for_kernel(self, kernel_id):
220 notebook_id = self.notebook_for_kernel(kernel_id)
221 if notebook_id is not None:
222 del self._notebook_mapping[notebook_id]
223
224 def start_kernel(self, notebook_id=None):
225 self.log.info
226 kernel_id = self._notebook_mapping.get(notebook_id)
227 if kernel_id is None:
228 kwargs = dict()
229 kwargs['extra_arguments'] = self.kernel_argv
230 kernel_id = self.kernel_manager.start_kernel(**kwargs)
231 if notebook_id is not None:
232 self._notebook_mapping[notebook_id] = kernel_id
233 self.log.info("Kernel started for notebook %s: %s" % (notebook_id,kernel_id))
234 self.log.debug("Kernel args: %r" % kwargs)
235 self.start_session_manager(kernel_id)
236 else:
237 self.log.info("Using existing kernel: %s" % kernel_id)
238 return kernel_id
239
240 def start_session_manager(self, kernel_id):
241 sm = self.kernel_manager.create_session_manager(kernel_id)
242 self._session_dict[kernel_id] = sm
243 iopub_stream = sm.get_iopub_stream()
244 shell_stream = sm.get_shell_stream()
245 iopub_router = IOPubStreamRouter(
246 zmq_stream=iopub_stream, session=sm.session, config=self.config
247 )
248 shell_router = ShellStreamRouter(
249 zmq_stream=shell_stream, session=sm.session, config=self.config
250 )
251 self._routers[(kernel_id, 'iopub')] = iopub_router
252 self._routers[(kernel_id, 'shell')] = shell_router
253
254 def kill_kernel(self, kernel_id):
255 if kernel_id not in self.kernel_manager:
256 raise web.HTTPError(404)
257 try:
258 sm = self._session_dict.pop(kernel_id)
259 except KeyError:
260 raise web.HTTPError(404)
261 sm.stop()
262 self.kernel_manager.kill_kernel(kernel_id)
263 self.delete_mapping_for_kernel(kernel_id)
264 self.log.info("Kernel killed: %s" % kernel_id)
265
266 def interrupt_kernel(self, kernel_id):
267 if kernel_id not in self.kernel_manager:
268 raise web.HTTPError(404)
269 self.kernel_manager.interrupt_kernel(kernel_id)
270 self.log.debug("Kernel interrupted: %s" % kernel_id)
271
272 def restart_kernel(self, kernel_id):
273 if kernel_id not in self.kernel_manager:
274 raise web.HTTPError(404)
275
276 # Get the notebook_id to preserve the kernel/notebook association
277 notebook_id = self.notebook_for_kernel(kernel_id)
278 # Create the new kernel first so we can move the clients over.
279 new_kernel_id = self.start_kernel()
280
281 # Copy the clients over to the new routers.
282 old_iopub_router = self.get_router(kernel_id, 'iopub')
283 old_shell_router = self.get_router(kernel_id, 'shell')
284 new_iopub_router = self.get_router(new_kernel_id, 'iopub')
285 new_shell_router = self.get_router(new_kernel_id, 'shell')
286 new_iopub_router.copy_clients(old_iopub_router)
287 new_shell_router.copy_clients(old_shell_router)
288
289 # Now shutdown the old session and the kernel.
290 # TODO: This causes a hard crash in ZMQStream.close, which sets
291 # self.socket to None to hastily. We will need to fix this in PyZMQ
292 # itself. For now, we just leave the old kernel running :(
293 # Maybe this is fixed now, but nothing was changed really.
294 self.kill_kernel(kernel_id)
295
296 # Now save the new kernel/notebook association. We have to save it
297 # after the old kernel is killed as that will delete the mapping.
298 self._notebook_mapping[notebook_id] = kernel_id
299
300 self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
301 return new_kernel_id
302
303 def get_router(self, kernel_id, stream_name):
304 router = self._routers[(kernel_id, stream_name)]
305 return router
306
@@ -27,14 +27,13 b' tornado.ioloop = ioloop'
27 27 from tornado import httpserver
28 28 from tornado import web
29 29
30 from .kernelmanager import KernelManager
30 from .kernelmanager import KernelManager, RoutingKernelManager
31 31 from .sessionmanager import SessionManager
32 32 from .handlers import (
33 33 NBBrowserHandler, NewHandler, NamedNotebookHandler,
34 KernelHandler, KernelActionHandler, ZMQStreamHandler,
34 MainKernelHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
35 35 NotebookRootHandler, NotebookHandler
36 36 )
37 from .routers import IOPubStreamRouter, ShellStreamRouter
38 37 from .notebookmanager import NotebookManager
39 38
40 39 from IPython.core.application import BaseIPythonApplication
@@ -65,12 +64,13 b" LOCALHOST = '127.0.0.1'"
65 64
66 65 class NotebookWebApplication(web.Application):
67 66
68 def __init__(self, kernel_manager, log, kernel_argv, config):
67 def __init__(self, routing_kernel_manager, notebook_manager, log):
69 68 handlers = [
70 69 (r"/", NBBrowserHandler),
71 70 (r"/new", NewHandler),
72 71 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
73 (r"/kernels", KernelHandler),
72 (r"/kernels", MainKernelHandler),
73 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
74 74 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
75 75 (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
76 76 (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
@@ -83,82 +83,10 b' class NotebookWebApplication(web.Application):'
83 83 )
84 84 web.Application.__init__(self, handlers, **settings)
85 85
86 self.kernel_manager = kernel_manager
86 self.routing_kernel_manager = routing_kernel_manager
87 87 self.log = log
88 self.kernel_argv = kernel_argv
89 self.config = config
90 self._routers = {}
91 self._session_dict = {}
92 self.notebook_manager = NotebookManager(config=self.config)
93
94 #-------------------------------------------------------------------------
95 # Methods for managing kernels and sessions
96 #-------------------------------------------------------------------------
97
98 @property
99 def kernel_ids(self):
100 return self.kernel_manager.kernel_ids
101
102 def start_kernel(self):
103 kwargs = dict()
104 kwargs['extra_arguments'] = self.kernel_argv
105 kernel_id = self.kernel_manager.start_kernel(**kwargs)
106 self.log.info("Kernel started: %s" % kernel_id)
107 self.log.debug("Kernel args: %r" % kwargs)
108 self.start_session_manager(kernel_id)
109 return kernel_id
110
111 def start_session_manager(self, kernel_id):
112 sm = self.kernel_manager.create_session_manager(kernel_id)
113 self._session_dict[kernel_id] = sm
114 iopub_stream = sm.get_iopub_stream()
115 shell_stream = sm.get_shell_stream()
116 iopub_router = IOPubStreamRouter(
117 zmq_stream=iopub_stream, session=sm.session, config=self.config
118 )
119 shell_router = ShellStreamRouter(
120 zmq_stream=shell_stream, session=sm.session, config=self.config
121 )
122 self._routers[(kernel_id, 'iopub')] = iopub_router
123 self._routers[(kernel_id, 'shell')] = shell_router
124
125 def kill_kernel(self, kernel_id):
126 sm = self._session_dict.pop(kernel_id)
127 sm.stop()
128 self.kernel_manager.kill_kernel(kernel_id)
129 self.log.info("Kernel killed: %s" % kernel_id)
130
131 def interrupt_kernel(self, kernel_id):
132 self.kernel_manager.interrupt_kernel(kernel_id)
133 self.log.debug("Kernel interrupted: %s" % kernel_id)
134
135 def restart_kernel(self, kernel_id):
136 # Create the new kernel first so we can move the clients over.
137 new_kernel_id = self.start_kernel()
138
139 # Copy the clients over to the new routers.
140 old_iopub_router = self.get_router(kernel_id, 'iopub')
141 old_shell_router = self.get_router(kernel_id, 'shell')
142 new_iopub_router = self.get_router(new_kernel_id, 'iopub')
143 new_shell_router = self.get_router(new_kernel_id, 'shell')
144 new_iopub_router.copy_clients(old_iopub_router)
145 new_shell_router.copy_clients(old_shell_router)
146
147 # Now shutdown the old session and the kernel.
148 # TODO: This causes a hard crash in ZMQStream.close, which sets
149 # self.socket to None to hastily. We will need to fix this in PyZMQ
150 # itself. For now, we just leave the old kernel running :(
151 # Maybe this is fixed now, but nothing was changed really.
152 self.kill_kernel(kernel_id)
153
154 self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
155 return new_kernel_id
156
157 def get_router(self, kernel_id, stream_name):
158 router = self._routers[(kernel_id, stream_name)]
159 return router
88 self.notebook_manager = notebook_manager
160 89
161
162 90
163 91 #-----------------------------------------------------------------------------
164 92 # Aliases and Flags
@@ -196,6 +124,7 b' class IPythonNotebookApp(BaseIPythonApplication):'
196 124 """
197 125
198 126 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
127 RoutingKernelManager, NotebookManager,
199 128 KernelManager, SessionManager, RichIPythonWidget]
200 129 flags = Dict(flags)
201 130 aliases = Dict(aliases)
@@ -232,12 +161,16 b' class IPythonNotebookApp(BaseIPythonApplication):'
232 161 if a.startswith('-') and a.lstrip('-') in notebook_flags:
233 162 self.kernel_argv.remove(a)
234 163
235 def init_kernel_manager(self):
164 def init_configurables(self):
236 165 # Don't let Qt or ZMQ swallow KeyboardInterupts.
237 166 signal.signal(signal.SIGINT, signal.SIG_DFL)
238 167
239 168 # Create a KernelManager and start a kernel.
240 169 self.kernel_manager = KernelManager(config=self.config, log=self.log)
170 self.routing_kernel_manager = RoutingKernelManager(config=self.config, log=self.log,
171 kernel_manager=self.kernel_manager, kernel_argv=self.kernel_argv
172 )
173 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
241 174
242 175 def init_logging(self):
243 176 super(IPythonNotebookApp, self).init_logging()
@@ -248,9 +181,9 b' class IPythonNotebookApp(BaseIPythonApplication):'
248 181
249 182 def initialize(self, argv=None):
250 183 super(IPythonNotebookApp, self).initialize(argv)
251 self.init_kernel_manager()
184 self.init_configurables()
252 185 self.web_app = NotebookWebApplication(
253 self.kernel_manager, self.log, self.kernel_argv, self.config
186 self.routing_kernel_manager, self.notebook_manager, self.log
254 187 )
255 188 self.http_server = httpserver.HTTPServer(self.web_app)
256 189 self.http_server.listen(self.port)
@@ -15,7 +15,7 b' import uuid'
15 15
16 16 from tornado import web
17 17
18 from IPython.config.configurable import Configurable
18 from IPython.config.configurable import LoggingConfigurable
19 19 from IPython.nbformat import current
20 20 from IPython.utils.traitlets import Unicode, List, Dict
21 21
@@ -25,7 +25,7 b' from IPython.utils.traitlets import Unicode, List, Dict'
25 25 #-----------------------------------------------------------------------------
26 26
27 27
28 class NotebookManager(Configurable):
28 class NotebookManager(LoggingConfigurable):
29 29
30 30 notebook_dir = Unicode(os.getcwd())
31 31 filename_ext = Unicode(u'.ipynb')
@@ -28,9 +28,10 b' var IPython = (function (IPython) {'
28 28 return msg;
29 29 }
30 30
31 Kernel.prototype.start_kernel = function (callback) {
31 Kernel.prototype.start_kernel = function (notebook_id, callback) {
32 32 var that = this;
33 $.post(this.base_url,
33 var qs = $.param({notebook:notebook_id});
34 $.post(this.base_url + '?' + qs,
34 35 function (kernel_id) {
35 36 that._handle_start_kernel(kernel_id, callback);
36 37 },
@@ -391,7 +391,8 b' var IPython = (function (IPython) {'
391 391
392 392 Notebook.prototype.start_kernel = function () {
393 393 this.kernel = new IPython.Kernel();
394 this.kernel.start_kernel($.proxy(this.kernel_started, this));
394 var notebook_id = IPython.save_widget.get_notebook_id();
395 this.kernel.start_kernel(notebook_id, $.proxy(this.kernel_started, this));
395 396 };
396 397
397 398
General Comments 0
You need to be logged in to leave comments. Login now