##// 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 self.render('notebook.html', notebook_id=notebook_id)
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 def get(self):
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 def post(self):
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 self.set_header('Location', '/'+kernel_id)
56 self.set_header('Location', '/'+kernel_id)
49 self.finish(json.dumps(kernel_id))
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 class KernelActionHandler(web.RequestHandler):
71 class KernelActionHandler(web.RequestHandler):
53
72
54 def post(self, kernel_id, action):
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 if action == 'interrupt':
75 if action == 'interrupt':
57 self.application.interrupt_kernel(kernel_id)
76 rkm.interrupt_kernel(kernel_id)
77 self.set_status(204)
58 if action == 'restart':
78 if action == 'restart':
59 new_kernel_id = self.application.restart_kernel(kernel_id)
79 new_kernel_id = rkm.restart_kernel(kernel_id)
60 self.write(json.dumps(new_kernel_id))
80 self.write(json.dumps(new_kernel_id))
61 self.finish()
81 self.finish()
62
82
@@ -67,7 +87,8 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
67 self.stream_name = stream_name
87 self.stream_name = stream_name
68
88
69 def open(self, kernel_id):
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 self.client_id = self.router.register_client(self)
92 self.client_id = self.router.register_client(self)
72 logging.info("Connection open: %s, %s" % (kernel_id, self.client_id))
93 logging.info("Connection open: %s, %s" % (kernel_id, self.client_id))
73
94
@@ -79,6 +100,10 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
79 logging.info("Connection closed: %s" % self.client_id)
100 logging.info("Connection closed: %s" % self.client_id)
80
101
81
102
103 #-----------------------------------------------------------------------------
104 # Notebook web service handlers
105 #-----------------------------------------------------------------------------
106
82 class NotebookRootHandler(web.RequestHandler):
107 class NotebookRootHandler(web.RequestHandler):
83
108
84 def get(self):
109 def get(self):
@@ -17,9 +17,13 b' import uuid'
17
17
18 import zmq
18 import zmq
19
19
20 from tornado import web
21
22 from .routers import IOPubStreamRouter, ShellStreamRouter
23
20 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
21 from IPython.zmq.ipkernel import launch_kernel
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 # Classes
29 # Classes
@@ -55,7 +59,7 b' class KernelManager(LoggingConfigurable):'
55
59
56 def start_kernel(self, **kwargs):
60 def start_kernel(self, **kwargs):
57 """Start a new kernel."""
61 """Start a new kernel."""
58 kernel_id = str(uuid.uuid4())
62 kernel_id = unicode(uuid.uuid4())
59 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(**kwargs)
63 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(**kwargs)
60 # Store the information for contacting the kernel. This assumes the kernel is
64 # Store the information for contacting the kernel. This assumes the kernel is
61 # running on localhost.
65 # running on localhost.
@@ -186,3 +190,117 b' class KernelManager(LoggingConfigurable):'
186 config=self.config, context=self.context, log=self.log
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 from tornado import httpserver
27 from tornado import httpserver
28 from tornado import web
28 from tornado import web
29
29
30 from .kernelmanager import KernelManager
30 from .kernelmanager import KernelManager, RoutingKernelManager
31 from .sessionmanager import SessionManager
31 from .sessionmanager import SessionManager
32 from .handlers import (
32 from .handlers import (
33 NBBrowserHandler, NewHandler, NamedNotebookHandler,
33 NBBrowserHandler, NewHandler, NamedNotebookHandler,
34 KernelHandler, KernelActionHandler, ZMQStreamHandler,
34 MainKernelHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
35 NotebookRootHandler, NotebookHandler
35 NotebookRootHandler, NotebookHandler
36 )
36 )
37 from .routers import IOPubStreamRouter, ShellStreamRouter
38 from .notebookmanager import NotebookManager
37 from .notebookmanager import NotebookManager
39
38
40 from IPython.core.application import BaseIPythonApplication
39 from IPython.core.application import BaseIPythonApplication
@@ -65,12 +64,13 b" LOCALHOST = '127.0.0.1'"
65
64
66 class NotebookWebApplication(web.Application):
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 handlers = [
68 handlers = [
70 (r"/", NBBrowserHandler),
69 (r"/", NBBrowserHandler),
71 (r"/new", NewHandler),
70 (r"/new", NewHandler),
72 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
71 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
73 (r"/kernels", KernelHandler),
72 (r"/kernels", MainKernelHandler),
73 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
74 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
74 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
75 (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
75 (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
76 (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
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 web.Application.__init__(self, handlers, **settings)
84 web.Application.__init__(self, handlers, **settings)
85
85
86 self.kernel_manager = kernel_manager
86 self.routing_kernel_manager = routing_kernel_manager
87 self.log = log
87 self.log = log
88 self.kernel_argv = kernel_argv
88 self.notebook_manager = notebook_manager
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
160
89
161
162
90
163 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
164 # Aliases and Flags
92 # Aliases and Flags
@@ -196,6 +124,7 b' class IPythonNotebookApp(BaseIPythonApplication):'
196 """
124 """
197
125
198 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
126 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
127 RoutingKernelManager, NotebookManager,
199 KernelManager, SessionManager, RichIPythonWidget]
128 KernelManager, SessionManager, RichIPythonWidget]
200 flags = Dict(flags)
129 flags = Dict(flags)
201 aliases = Dict(aliases)
130 aliases = Dict(aliases)
@@ -232,12 +161,16 b' class IPythonNotebookApp(BaseIPythonApplication):'
232 if a.startswith('-') and a.lstrip('-') in notebook_flags:
161 if a.startswith('-') and a.lstrip('-') in notebook_flags:
233 self.kernel_argv.remove(a)
162 self.kernel_argv.remove(a)
234
163
235 def init_kernel_manager(self):
164 def init_configurables(self):
236 # Don't let Qt or ZMQ swallow KeyboardInterupts.
165 # Don't let Qt or ZMQ swallow KeyboardInterupts.
237 signal.signal(signal.SIGINT, signal.SIG_DFL)
166 signal.signal(signal.SIGINT, signal.SIG_DFL)
238
167
239 # Create a KernelManager and start a kernel.
168 # Create a KernelManager and start a kernel.
240 self.kernel_manager = KernelManager(config=self.config, log=self.log)
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 def init_logging(self):
175 def init_logging(self):
243 super(IPythonNotebookApp, self).init_logging()
176 super(IPythonNotebookApp, self).init_logging()
@@ -248,9 +181,9 b' class IPythonNotebookApp(BaseIPythonApplication):'
248
181
249 def initialize(self, argv=None):
182 def initialize(self, argv=None):
250 super(IPythonNotebookApp, self).initialize(argv)
183 super(IPythonNotebookApp, self).initialize(argv)
251 self.init_kernel_manager()
184 self.init_configurables()
252 self.web_app = NotebookWebApplication(
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 self.http_server = httpserver.HTTPServer(self.web_app)
188 self.http_server = httpserver.HTTPServer(self.web_app)
256 self.http_server.listen(self.port)
189 self.http_server.listen(self.port)
@@ -15,7 +15,7 b' import uuid'
15
15
16 from tornado import web
16 from tornado import web
17
17
18 from IPython.config.configurable import Configurable
18 from IPython.config.configurable import LoggingConfigurable
19 from IPython.nbformat import current
19 from IPython.nbformat import current
20 from IPython.utils.traitlets import Unicode, List, Dict
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 notebook_dir = Unicode(os.getcwd())
30 notebook_dir = Unicode(os.getcwd())
31 filename_ext = Unicode(u'.ipynb')
31 filename_ext = Unicode(u'.ipynb')
@@ -28,9 +28,10 b' var IPython = (function (IPython) {'
28 return msg;
28 return msg;
29 }
29 }
30
30
31 Kernel.prototype.start_kernel = function (callback) {
31 Kernel.prototype.start_kernel = function (notebook_id, callback) {
32 var that = this;
32 var that = this;
33 $.post(this.base_url,
33 var qs = $.param({notebook:notebook_id});
34 $.post(this.base_url + '?' + qs,
34 function (kernel_id) {
35 function (kernel_id) {
35 that._handle_start_kernel(kernel_id, callback);
36 that._handle_start_kernel(kernel_id, callback);
36 },
37 },
@@ -391,7 +391,8 b' var IPython = (function (IPython) {'
391
391
392 Notebook.prototype.start_kernel = function () {
392 Notebook.prototype.start_kernel = function () {
393 this.kernel = new IPython.Kernel();
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