##// END OF EJS Templates
Fixed subtle bug in kernel restarting....
Brian E. Granger -
Show More
@@ -1,160 +1,158 b''
1 """Tornado handlers for the notebook."""
1 """Tornado handlers for the notebook."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 import json
7 import json
8 import logging
8 import logging
9 import urllib
9 import urllib
10
10
11 from tornado import web
11 from tornado import web
12 from tornado import websocket
12 from tornado import websocket
13
13
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Top-level handlers
16 # Top-level handlers
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
19
20 class NBBrowserHandler(web.RequestHandler):
20 class NBBrowserHandler(web.RequestHandler):
21 def get(self):
21 def get(self):
22 nbm = self.application.notebook_manager
22 nbm = self.application.notebook_manager
23 project = nbm.notebook_dir
23 project = nbm.notebook_dir
24 self.render('nbbrowser.html', project=project)
24 self.render('nbbrowser.html', project=project)
25
25
26
26
27 class NewHandler(web.RequestHandler):
27 class NewHandler(web.RequestHandler):
28 def get(self):
28 def get(self):
29 notebook_id = self.application.notebook_manager.new_notebook()
29 notebook_id = self.application.notebook_manager.new_notebook()
30 self.render('notebook.html', notebook_id=notebook_id)
30 self.render('notebook.html', notebook_id=notebook_id)
31
31
32
32
33 class NamedNotebookHandler(web.RequestHandler):
33 class NamedNotebookHandler(web.RequestHandler):
34 def get(self, notebook_id):
34 def get(self, notebook_id):
35 nbm = self.application.notebook_manager
35 nbm = self.application.notebook_manager
36 if not nbm.notebook_exists(notebook_id):
36 if not nbm.notebook_exists(notebook_id):
37 raise web.HTTPError(404)
37 raise web.HTTPError(404)
38 self.render('notebook.html', notebook_id=notebook_id)
38 self.render('notebook.html', notebook_id=notebook_id)
39
39
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Kernel handlers
42 # Kernel handlers
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45
45
46 class MainKernelHandler(web.RequestHandler):
46 class MainKernelHandler(web.RequestHandler):
47
47
48 def get(self):
48 def get(self):
49 rkm = self.application.routing_kernel_manager
49 rkm = self.application.routing_kernel_manager
50 self.finish(json.dumps(rkm.kernel_ids))
50 self.finish(json.dumps(rkm.kernel_ids))
51
51
52 def post(self):
52 def post(self):
53 rkm = self.application.routing_kernel_manager
53 rkm = self.application.routing_kernel_manager
54 notebook_id = self.get_argument('notebook', default=None)
54 notebook_id = self.get_argument('notebook', default=None)
55 kernel_id = rkm.start_kernel(notebook_id)
55 kernel_id = rkm.start_kernel(notebook_id)
56 self.set_header('Location', '/'+kernel_id)
56 self.set_header('Location', '/'+kernel_id)
57 self.finish(json.dumps(kernel_id))
57 self.finish(json.dumps(kernel_id))
58
58
59
59
60 class KernelHandler(web.RequestHandler):
60 class KernelHandler(web.RequestHandler):
61
61
62 SUPPORTED_METHODS = ('DELETE')
62 SUPPORTED_METHODS = ('DELETE')
63
63
64 def delete(self, kernel_id):
64 def delete(self, kernel_id):
65 rkm = self.application.routing_kernel_manager
65 rkm = self.application.routing_kernel_manager
66 self.kill_kernel(kernel_id)
66 self.kill_kernel(kernel_id)
67 self.set_status(204)
67 self.set_status(204)
68 self.finish()
68 self.finish()
69
69
70
70
71 class KernelActionHandler(web.RequestHandler):
71 class KernelActionHandler(web.RequestHandler):
72
72
73 def post(self, kernel_id, action):
73 def post(self, kernel_id, action):
74 rkm = self.application.routing_kernel_manager
74 rkm = self.application.routing_kernel_manager
75 if action == 'interrupt':
75 if action == 'interrupt':
76 rkm.interrupt_kernel(kernel_id)
76 rkm.interrupt_kernel(kernel_id)
77 self.set_status(204)
77 self.set_status(204)
78 if action == 'restart':
78 if action == 'restart':
79 new_kernel_id = rkm.restart_kernel(kernel_id)
79 new_kernel_id = rkm.restart_kernel(kernel_id)
80 self.write(json.dumps(new_kernel_id))
80 self.write(json.dumps(new_kernel_id))
81 self.finish()
81 self.finish()
82
82
83
83
84 class ZMQStreamHandler(websocket.WebSocketHandler):
84 class ZMQStreamHandler(websocket.WebSocketHandler):
85
85
86 def initialize(self, stream_name):
86 def initialize(self, stream_name):
87 self.stream_name = stream_name
87 self.stream_name = stream_name
88
88
89 def open(self, kernel_id):
89 def open(self, kernel_id):
90 rkm = self.application.routing_kernel_manager
90 rkm = self.application.routing_kernel_manager
91 self.router = rkm.get_router(kernel_id, self.stream_name)
91 self.router = rkm.get_router(kernel_id, self.stream_name)
92 self.client_id = self.router.register_client(self)
92 self.client_id = self.router.register_client(self)
93 logging.info("Connection open: %s, %s" % (kernel_id, self.client_id))
94
93
95 def on_message(self, msg):
94 def on_message(self, msg):
96 self.router.forward_msg(self.client_id, msg)
95 self.router.forward_msg(self.client_id, msg)
97
96
98 def on_close(self):
97 def on_close(self):
99 self.router.unregister_client(self.client_id)
98 self.router.unregister_client(self.client_id)
100 logging.info("Connection closed: %s" % self.client_id)
101
99
102
100
103 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
104 # Notebook web service handlers
102 # Notebook web service handlers
105 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
106
104
107 class NotebookRootHandler(web.RequestHandler):
105 class NotebookRootHandler(web.RequestHandler):
108
106
109 def get(self):
107 def get(self):
110 nbm = self.application.notebook_manager
108 nbm = self.application.notebook_manager
111 files = nbm.list_notebooks()
109 files = nbm.list_notebooks()
112 self.finish(json.dumps(files))
110 self.finish(json.dumps(files))
113
111
114 def post(self):
112 def post(self):
115 nbm = self.application.notebook_manager
113 nbm = self.application.notebook_manager
116 body = self.request.body.strip()
114 body = self.request.body.strip()
117 format = self.get_argument('format', default='json')
115 format = self.get_argument('format', default='json')
118 name = self.get_argument('name', default=None)
116 name = self.get_argument('name', default=None)
119 if body:
117 if body:
120 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
118 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
121 else:
119 else:
122 notebook_id = nbm.new_notebook()
120 notebook_id = nbm.new_notebook()
123 self.set_header('Location', '/'+notebook_id)
121 self.set_header('Location', '/'+notebook_id)
124 self.finish(json.dumps(notebook_id))
122 self.finish(json.dumps(notebook_id))
125
123
126
124
127 class NotebookHandler(web.RequestHandler):
125 class NotebookHandler(web.RequestHandler):
128
126
129 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
127 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
130
128
131 def get(self, notebook_id):
129 def get(self, notebook_id):
132 nbm = self.application.notebook_manager
130 nbm = self.application.notebook_manager
133 format = self.get_argument('format', default='json')
131 format = self.get_argument('format', default='json')
134 last_mod, name, data = nbm.get_notebook(notebook_id, format)
132 last_mod, name, data = nbm.get_notebook(notebook_id, format)
135 if format == u'json':
133 if format == u'json':
136 self.set_header('Content-Type', 'application/json')
134 self.set_header('Content-Type', 'application/json')
137 self.set_header('Content-Disposition','attachment; filename=%s.json' % name)
135 self.set_header('Content-Disposition','attachment; filename=%s.json' % name)
138 elif format == u'xml':
136 elif format == u'xml':
139 self.set_header('Content-Type', 'application/xml')
137 self.set_header('Content-Type', 'application/xml')
140 self.set_header('Content-Disposition','attachment; filename=%s.ipynb' % name)
138 self.set_header('Content-Disposition','attachment; filename=%s.ipynb' % name)
141 elif format == u'py':
139 elif format == u'py':
142 self.set_header('Content-Type', 'application/x-python')
140 self.set_header('Content-Type', 'application/x-python')
143 self.set_header('Content-Disposition','attachment; filename=%s.py' % name)
141 self.set_header('Content-Disposition','attachment; filename=%s.py' % name)
144 self.set_header('Last-Modified', last_mod)
142 self.set_header('Last-Modified', last_mod)
145 self.finish(data)
143 self.finish(data)
146
144
147 def put(self, notebook_id):
145 def put(self, notebook_id):
148 nbm = self.application.notebook_manager
146 nbm = self.application.notebook_manager
149 format = self.get_argument('format', default='json')
147 format = self.get_argument('format', default='json')
150 name = self.get_argument('name', default=None)
148 name = self.get_argument('name', default=None)
151 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
149 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
152 self.set_status(204)
150 self.set_status(204)
153 self.finish()
151 self.finish()
154
152
155 def delete(self, notebook_id):
153 def delete(self, notebook_id):
156 nbm = self.application.notebook_manager
154 nbm = self.application.notebook_manager
157 nbm.delete_notebook(notebook_id)
155 nbm.delete_notebook(notebook_id)
158 self.set_status(204)
156 self.set_status(204)
159 self.finish()
157 self.finish()
160
158
@@ -1,306 +1,350 b''
1 """A kernel manager for multiple kernels."""
1 """A kernel manager for multiple kernels."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
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.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 import signal
14 import signal
15 import sys
15 import sys
16 import uuid
16 import uuid
17
17
18 import zmq
18 import zmq
19
19
20 from tornado import web
20 from tornado import web
21
21
22 from .routers import IOPubStreamRouter, ShellStreamRouter
22 from .routers import IOPubStreamRouter, ShellStreamRouter
23
23
24 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.zmq.ipkernel import launch_kernel
25 from IPython.zmq.ipkernel import launch_kernel
26 from IPython.utils.traitlets import Instance, Dict, List, Unicode
26 from IPython.utils.traitlets import Instance, Dict, List, Unicode
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Classes
29 # Classes
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 class DuplicateKernelError(Exception):
32 class DuplicateKernelError(Exception):
33 pass
33 pass
34
34
35
35
36 class KernelManager(LoggingConfigurable):
36 class KernelManager(LoggingConfigurable):
37 """A class for managing multiple kernels."""
37 """A class for managing multiple kernels."""
38
38
39 context = Instance('zmq.Context')
39 context = Instance('zmq.Context')
40 def _context_default(self):
40 def _context_default(self):
41 return zmq.Context.instance()
41 return zmq.Context.instance()
42
42
43 _kernels = Dict()
43 _kernels = Dict()
44
44
45 @property
45 @property
46 def kernel_ids(self):
46 def kernel_ids(self):
47 """Return a list of the kernel ids of the active kernels."""
47 """Return a list of the kernel ids of the active kernels."""
48 return self._kernels.keys()
48 return self._kernels.keys()
49
49
50 def __len__(self):
50 def __len__(self):
51 """Return the number of running kernels."""
51 """Return the number of running kernels."""
52 return len(self.kernel_ids)
52 return len(self.kernel_ids)
53
53
54 def __contains__(self, kernel_id):
54 def __contains__(self, kernel_id):
55 if kernel_id in self.kernel_ids:
55 if kernel_id in self.kernel_ids:
56 return True
56 return True
57 else:
57 else:
58 return False
58 return False
59
59
60 def start_kernel(self, **kwargs):
60 def start_kernel(self, **kwargs):
61 """Start a new kernel."""
61 """Start a new kernel."""
62 kernel_id = unicode(uuid.uuid4())
62 kernel_id = unicode(uuid.uuid4())
63 (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)
64 # 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
65 # running on localhost.
65 # running on localhost.
66 d = dict(
66 d = dict(
67 process = process,
67 process = process,
68 stdin_port = stdin_port,
68 stdin_port = stdin_port,
69 iopub_port = iopub_port,
69 iopub_port = iopub_port,
70 shell_port = shell_port,
70 shell_port = shell_port,
71 hb_port = hb_port,
71 hb_port = hb_port,
72 ip = '127.0.0.1'
72 ip = '127.0.0.1'
73 )
73 )
74 self._kernels[kernel_id] = d
74 self._kernels[kernel_id] = d
75 return kernel_id
75 return kernel_id
76
76
77 def kill_kernel(self, kernel_id):
77 def kill_kernel(self, kernel_id):
78 """Kill a kernel by its kernel uuid.
78 """Kill a kernel by its kernel uuid.
79
79
80 Parameters
80 Parameters
81 ==========
81 ==========
82 kernel_id : uuid
82 kernel_id : uuid
83 The id of the kernel to kill.
83 The id of the kernel to kill.
84 """
84 """
85 kernel_process = self.get_kernel_process(kernel_id)
85 kernel_process = self.get_kernel_process(kernel_id)
86 if kernel_process is not None:
86 if kernel_process is not None:
87 # Attempt to kill the kernel.
87 # Attempt to kill the kernel.
88 try:
88 try:
89 kernel_process.kill()
89 kernel_process.kill()
90 except OSError, e:
90 except OSError, e:
91 # In Windows, we will get an Access Denied error if the process
91 # In Windows, we will get an Access Denied error if the process
92 # has already terminated. Ignore it.
92 # has already terminated. Ignore it.
93 if not (sys.platform == 'win32' and e.winerror == 5):
93 if not (sys.platform == 'win32' and e.winerror == 5):
94 raise
94 raise
95 del self._kernels[kernel_id]
95 del self._kernels[kernel_id]
96
96
97 def interrupt_kernel(self, kernel_id):
97 def interrupt_kernel(self, kernel_id):
98 """Interrupt (SIGINT) the kernel by its uuid.
98 """Interrupt (SIGINT) the kernel by its uuid.
99
99
100 Parameters
100 Parameters
101 ==========
101 ==========
102 kernel_id : uuid
102 kernel_id : uuid
103 The id of the kernel to interrupt.
103 The id of the kernel to interrupt.
104 """
104 """
105 kernel_process = self.get_kernel_process(kernel_id)
105 kernel_process = self.get_kernel_process(kernel_id)
106 if kernel_process is not None:
106 if kernel_process is not None:
107 if sys.platform == 'win32':
107 if sys.platform == 'win32':
108 from parentpoller import ParentPollerWindows as Poller
108 from parentpoller import ParentPollerWindows as Poller
109 Poller.send_interrupt(kernel_process.win32_interrupt_event)
109 Poller.send_interrupt(kernel_process.win32_interrupt_event)
110 else:
110 else:
111 kernel_process.send_signal(signal.SIGINT)
111 kernel_process.send_signal(signal.SIGINT)
112
112
113 def signal_kernel(self, kernel_id, signum):
113 def signal_kernel(self, kernel_id, signum):
114 """ Sends a signal to the kernel by its uuid.
114 """ Sends a signal to the kernel by its uuid.
115
115
116 Note that since only SIGTERM is supported on Windows, this function
116 Note that since only SIGTERM is supported on Windows, this function
117 is only useful on Unix systems.
117 is only useful on Unix systems.
118
118
119 Parameters
119 Parameters
120 ==========
120 ==========
121 kernel_id : uuid
121 kernel_id : uuid
122 The id of the kernel to signal.
122 The id of the kernel to signal.
123 """
123 """
124 kernel_process = self.get_kernel_process(kernel_id)
124 kernel_process = self.get_kernel_process(kernel_id)
125 if kernel_process is not None:
125 if kernel_process is not None:
126 kernel_process.send_signal(signum)
126 kernel_process.send_signal(signum)
127
127
128 def get_kernel_process(self, kernel_id):
128 def get_kernel_process(self, kernel_id):
129 """Get the process object for a kernel by its uuid.
129 """Get the process object for a kernel by its uuid.
130
130
131 Parameters
131 Parameters
132 ==========
132 ==========
133 kernel_id : uuid
133 kernel_id : uuid
134 The id of the kernel.
134 The id of the kernel.
135 """
135 """
136 d = self._kernels.get(kernel_id)
136 d = self._kernels.get(kernel_id)
137 if d is not None:
137 if d is not None:
138 return d['process']
138 return d['process']
139 else:
139 else:
140 raise KeyError("Kernel with id not found: %s" % kernel_id)
140 raise KeyError("Kernel with id not found: %s" % kernel_id)
141
141
142 def get_kernel_ports(self, kernel_id):
142 def get_kernel_ports(self, kernel_id):
143 """Return a dictionary of ports for a kernel.
143 """Return a dictionary of ports for a kernel.
144
144
145 Parameters
145 Parameters
146 ==========
146 ==========
147 kernel_id : uuid
147 kernel_id : uuid
148 The id of the kernel.
148 The id of the kernel.
149
149
150 Returns
150 Returns
151 =======
151 =======
152 port_dict : dict
152 port_dict : dict
153 A dict of key, value pairs where the keys are the names
153 A dict of key, value pairs where the keys are the names
154 (stdin_port,iopub_port,shell_port) and the values are the
154 (stdin_port,iopub_port,shell_port) and the values are the
155 integer port numbers for those channels.
155 integer port numbers for those channels.
156 """
156 """
157 d = self._kernels.get(kernel_id)
157 d = self._kernels.get(kernel_id)
158 if d is not None:
158 if d is not None:
159 dcopy = d.copy()
159 dcopy = d.copy()
160 dcopy.pop('process')
160 dcopy.pop('process')
161 dcopy.pop('ip')
161 dcopy.pop('ip')
162 return dcopy
162 return dcopy
163 else:
163 else:
164 raise KeyError("Kernel with id not found: %s" % kernel_id)
164 raise KeyError("Kernel with id not found: %s" % kernel_id)
165
165
166 def get_kernel_ip(self, kernel_id):
166 def get_kernel_ip(self, kernel_id):
167 """Return ip address for a kernel.
167 """Return ip address for a kernel.
168
168
169 Parameters
169 Parameters
170 ==========
170 ==========
171 kernel_id : uuid
171 kernel_id : uuid
172 The id of the kernel.
172 The id of the kernel.
173
173
174 Returns
174 Returns
175 =======
175 =======
176 ip : str
176 ip : str
177 The ip address of the kernel.
177 The ip address of the kernel.
178 """
178 """
179 d = self._kernels.get(kernel_id)
179 d = self._kernels.get(kernel_id)
180 if d is not None:
180 if d is not None:
181 return d['ip']
181 return d['ip']
182 else:
182 else:
183 raise KeyError("Kernel with id not found: %s" % kernel_id)
183 raise KeyError("Kernel with id not found: %s" % kernel_id)
184
184
185 def create_session_manager(self, kernel_id):
185 def create_session_manager(self, kernel_id):
186 """Create a new session manager for a kernel by its uuid."""
186 """Create a new session manager for a kernel by its uuid."""
187 from sessionmanager import SessionManager
187 from sessionmanager import SessionManager
188 return SessionManager(
188 return SessionManager(
189 kernel_id=kernel_id, kernel_manager=self,
189 kernel_id=kernel_id, kernel_manager=self,
190 config=self.config, context=self.context, log=self.log
190 config=self.config, context=self.context, log=self.log
191 )
191 )
192
192
193
193
194 class RoutingKernelManager(LoggingConfigurable):
194 class RoutingKernelManager(LoggingConfigurable):
195 """A KernelManager that handles WebSocket routing and HTTP error handling"""
195 """A KernelManager that handles WebSocket routing and HTTP error handling"""
196
196
197 kernel_argv = List(Unicode)
197 kernel_argv = List(Unicode)
198 kernel_manager = Instance(KernelManager)
198 kernel_manager = Instance(KernelManager)
199
199
200 _routers = Dict()
200 _routers = Dict()
201 _session_dict = Dict()
201 _session_dict = Dict()
202 _notebook_mapping = Dict()
202 _notebook_mapping = Dict()
203
203
204 #-------------------------------------------------------------------------
204 #-------------------------------------------------------------------------
205 # Methods for managing kernels and sessions
205 # Methods for managing kernels and sessions
206 #-------------------------------------------------------------------------
206 #-------------------------------------------------------------------------
207
207
208 @property
208 @property
209 def kernel_ids(self):
209 def kernel_ids(self):
210 """List the kernel ids."""
210 return self.kernel_manager.kernel_ids
211 return self.kernel_manager.kernel_ids
211
212
213 def kernel_for_notebook(self, notebook_id):
214 """Return the kernel_id for a notebook_id or None."""
215 return self._notebook_mapping.get(notebook_id)
216
217 def set_kernel_for_notebook(self, notebook_id, kernel_id):
218 """Associate a notebook with a kernel."""
219 if notebook_id is not None:
220 self._notebook_mapping[notebook_id] = kernel_id
221
212 def notebook_for_kernel(self, kernel_id):
222 def notebook_for_kernel(self, kernel_id):
223 """Return the notebook_id for a kernel_id or None."""
213 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
224 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
214 if len(notebook_ids) == 1:
225 if len(notebook_ids) == 1:
215 return notebook_ids[0]
226 return notebook_ids[0]
216 else:
227 else:
217 return None
228 return None
218
229
219 def delete_mapping_for_kernel(self, kernel_id):
230 def delete_mapping_for_kernel(self, kernel_id):
231 """Remove the kernel/notebook mapping for kernel_id."""
220 notebook_id = self.notebook_for_kernel(kernel_id)
232 notebook_id = self.notebook_for_kernel(kernel_id)
221 if notebook_id is not None:
233 if notebook_id is not None:
222 del self._notebook_mapping[notebook_id]
234 del self._notebook_mapping[notebook_id]
223
235
224 def start_kernel(self, notebook_id=None):
236 def start_kernel(self, notebook_id=None):
237 """Start a kernel an return its kernel_id.
238
239 Parameters
240 ----------
241 notebook_id : uuid
242 The uuid of the notebook to associate the new kernel with. If this
243 is not None, this kernel will be persistent whenever the notebook
244 requests a kernel.
245 """
225 self.log.info
246 self.log.info
226 kernel_id = self._notebook_mapping.get(notebook_id)
247 kernel_id = self.kernel_for_notebook(notebook_id)
227 if kernel_id is None:
248 if kernel_id is None:
228 kwargs = dict()
249 kwargs = dict()
229 kwargs['extra_arguments'] = self.kernel_argv
250 kwargs['extra_arguments'] = self.kernel_argv
230 kernel_id = self.kernel_manager.start_kernel(**kwargs)
251 kernel_id = self.kernel_manager.start_kernel(**kwargs)
231 if notebook_id is not None:
252 self.set_kernel_for_notebook(notebook_id, kernel_id)
232 self._notebook_mapping[notebook_id] = kernel_id
253 self.log.info("Kernel started: %s" % kernel_id)
233 self.log.info("Kernel started for notebook %s: %s" % (notebook_id,kernel_id))
234 self.log.debug("Kernel args: %r" % kwargs)
254 self.log.debug("Kernel args: %r" % kwargs)
235 self.start_session_manager(kernel_id)
255 self.start_session_manager(kernel_id)
236 else:
256 else:
237 self.log.info("Using existing kernel: %s" % kernel_id)
257 self.log.info("Using existing kernel: %s" % kernel_id)
238 return kernel_id
258 return kernel_id
239
259
240 def start_session_manager(self, kernel_id):
260 def start_session_manager(self, kernel_id):
261 """Start the ZMQ sockets (a "session") to connect to a kernel."""
241 sm = self.kernel_manager.create_session_manager(kernel_id)
262 sm = self.kernel_manager.create_session_manager(kernel_id)
242 self._session_dict[kernel_id] = sm
263 self._session_dict[kernel_id] = sm
243 iopub_stream = sm.get_iopub_stream()
264 iopub_stream = sm.get_iopub_stream()
244 shell_stream = sm.get_shell_stream()
265 shell_stream = sm.get_shell_stream()
245 iopub_router = IOPubStreamRouter(
266 iopub_router = IOPubStreamRouter(
246 zmq_stream=iopub_stream, session=sm.session, config=self.config
267 zmq_stream=iopub_stream, session=sm.session, config=self.config
247 )
268 )
248 shell_router = ShellStreamRouter(
269 shell_router = ShellStreamRouter(
249 zmq_stream=shell_stream, session=sm.session, config=self.config
270 zmq_stream=shell_stream, session=sm.session, config=self.config
250 )
271 )
251 self._routers[(kernel_id, 'iopub')] = iopub_router
272 self.set_router(kernel_id, 'iopub', iopub_router)
252 self._routers[(kernel_id, 'shell')] = shell_router
273 self.set_router(kernel_id, 'shell', shell_router)
253
274
254 def kill_kernel(self, kernel_id):
275 def kill_kernel(self, kernel_id):
276 """Kill a kernel and remove its notebook association."""
255 if kernel_id not in self.kernel_manager:
277 if kernel_id not in self.kernel_manager:
256 raise web.HTTPError(404)
278 raise web.HTTPError(404)
257 try:
279 try:
258 sm = self._session_dict.pop(kernel_id)
280 sm = self._session_dict.pop(kernel_id)
259 except KeyError:
281 except KeyError:
260 raise web.HTTPError(404)
282 raise web.HTTPError(404)
261 sm.stop()
283 sm.stop()
262 self.kernel_manager.kill_kernel(kernel_id)
284 self.kernel_manager.kill_kernel(kernel_id)
263 self.delete_mapping_for_kernel(kernel_id)
285 self.delete_mapping_for_kernel(kernel_id)
264 self.log.info("Kernel killed: %s" % kernel_id)
286 self.log.info("Kernel killed: %s" % kernel_id)
265
287
266 def interrupt_kernel(self, kernel_id):
288 def interrupt_kernel(self, kernel_id):
289 """Interrupt a kernel."""
267 if kernel_id not in self.kernel_manager:
290 if kernel_id not in self.kernel_manager:
268 raise web.HTTPError(404)
291 raise web.HTTPError(404)
269 self.kernel_manager.interrupt_kernel(kernel_id)
292 self.kernel_manager.interrupt_kernel(kernel_id)
270 self.log.debug("Kernel interrupted: %s" % kernel_id)
293 self.log.debug("Kernel interrupted: %s" % kernel_id)
271
294
272 def restart_kernel(self, kernel_id):
295 def restart_kernel(self, kernel_id):
296 """Restart a kernel while keeping clients connected."""
273 if kernel_id not in self.kernel_manager:
297 if kernel_id not in self.kernel_manager:
274 raise web.HTTPError(404)
298 raise web.HTTPError(404)
275
299
276 # Get the notebook_id to preserve the kernel/notebook association
300 # Get the notebook_id to preserve the kernel/notebook association
277 notebook_id = self.notebook_for_kernel(kernel_id)
301 notebook_id = self.notebook_for_kernel(kernel_id)
278 # Create the new kernel first so we can move the clients over.
302 # Create the new kernel first so we can move the clients over.
279 new_kernel_id = self.start_kernel()
303 new_kernel_id = self.start_kernel()
280
304
281 # Copy the clients over to the new routers.
305 # Copy the clients over to the new routers.
282 old_iopub_router = self.get_router(kernel_id, 'iopub')
306 old_iopub_router = self.get_router(kernel_id, 'iopub')
283 old_shell_router = self.get_router(kernel_id, 'shell')
307 old_shell_router = self.get_router(kernel_id, 'shell')
284 new_iopub_router = self.get_router(new_kernel_id, 'iopub')
308 new_iopub_router = self.get_router(new_kernel_id, 'iopub')
285 new_shell_router = self.get_router(new_kernel_id, 'shell')
309 new_shell_router = self.get_router(new_kernel_id, 'shell')
286 new_iopub_router.copy_clients(old_iopub_router)
310 new_iopub_router.copy_clients(old_iopub_router)
287 new_shell_router.copy_clients(old_shell_router)
311 new_shell_router.copy_clients(old_shell_router)
288
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
289 # Now shutdown the old session and the kernel.
321 # Now shutdown the old session and the kernel.
290 # TODO: This causes a hard crash in ZMQStream.close, which sets
322 # 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
323 # 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 :(
324 # itself. For now, we just leave the old kernel running :(
293 # Maybe this is fixed now, but nothing was changed really.
325 # Maybe this is fixed now, but nothing was changed really.
294 self.kill_kernel(kernel_id)
326 self.kill_kernel(kernel_id)
295
327
296 # Now save the new kernel/notebook association. We have to save it
328 # 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.
329 # after the old kernel is killed as that will delete the mapping.
298 self._notebook_mapping[notebook_id] = kernel_id
330 self.set_kernel_for_notebook(notebook_id, new_kernel_id)
299
331
300 self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
332 self.log.debug("Kernel restarted: %s" % new_kernel_id)
301 return new_kernel_id
333 return new_kernel_id
302
334
303 def get_router(self, kernel_id, stream_name):
335 def get_router(self, kernel_id, stream_name):
336 """Return the router for a given kernel_id and stream name."""
304 router = self._routers[(kernel_id, stream_name)]
337 router = self._routers[(kernel_id, stream_name)]
305 return router
338 return router
306
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
@@ -1,118 +1,125 b''
1 """Routers that connect WebSockets to ZMQ sockets."""
1 """Routers that connect WebSockets to ZMQ sockets."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
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.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 import uuid
14 import uuid
15 from Queue import Queue
15 from Queue import Queue
16 import json
16 import json
17
17
18 from IPython.config.configurable import Configurable
18 from IPython.config.configurable import Configurable
19 from IPython.utils.traitlets import Instance, Int, Dict
19 from IPython.utils.traitlets import Instance, Int, Dict
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Classes
22 # Classes
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 class ZMQStreamRouter(Configurable):
25 class ZMQStreamRouter(Configurable):
26
26
27 zmq_stream = Instance('zmq.eventloop.zmqstream.ZMQStream')
27 zmq_stream = Instance('zmq.eventloop.zmqstream.ZMQStream')
28 session = Instance('IPython.zmq.session.Session')
28 session = Instance('IPython.zmq.session.Session')
29 max_msg_size = Int(2048, config=True, help="""
29 max_msg_size = Int(2048, config=True, help="""
30 The max raw message size accepted from the browser
30 The max raw message size accepted from the browser
31 over a WebSocket connection.
31 over a WebSocket connection.
32 """)
32 """)
33
33
34 _clients = Dict()
34 _clients = Dict()
35
35
36 def __init__(self, **kwargs):
36 def __init__(self, **kwargs):
37 super(ZMQStreamRouter,self).__init__(**kwargs)
37 super(ZMQStreamRouter,self).__init__(**kwargs)
38 self.zmq_stream.on_recv(self._on_zmq_reply)
38 self.zmq_stream.on_recv(self._on_zmq_reply)
39
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
40 def register_client(self, client):
48 def register_client(self, client):
41 """Register a client, returning a client uuid."""
49 """Register a client, returning a client uuid."""
42 client_id = uuid.uuid4()
50 client_id = unicode(uuid.uuid4())
43 self._clients[client_id] = client
51 self._clients[client_id] = client
44 return client_id
52 return client_id
45
53
46 def unregister_client(self, client_id):
54 def unregister_client(self, client_id):
47 """Unregister a client by its client uuid."""
55 """Unregister a client by its client uuid."""
48 del self._clients[client_id]
56 del self._clients[client_id]
49
57
50 def copy_clients(self, router):
58 def copy_clients(self, router):
51 """Copy the clients of another router to this one.
59 """Copy the clients of another router to this one.
52
60
53 This is used to enable the backend zeromq stream to disconnect
61 This is used to enable the backend zeromq stream to disconnect
54 and reconnect while the WebSocket connections to browsers
62 and reconnect while the WebSocket connections to browsers
55 remain, such as when a kernel is restarted.
63 remain, such as when a kernel is restarted.
56 """
64 """
57 for client_id, client in router._clients.items():
65 for client_id, client in router._clients.items():
58 client.router = self
66 client.router = self
59 self._clients[client_id] = client
67 self._clients[client_id] = client
60
68
61 def forward_msg(self, client_id, msg):
69 def forward_msg(self, client_id, msg):
62 """Forward a msg to a client by its id.
70 """Forward a msg to a client by its id.
63
71
64 The default implementation of this will fail silently if a message
72 The default implementation of this will fail silently if a message
65 arrives on a socket that doesn't support it. This method should
73 arrives on a socket that doesn't support it. This method should
66 use max_msg_size to check and silently discard message that are too
74 use max_msg_size to check and silently discard message that are too
67 long."""
75 long."""
68 pass
76 pass
69
77
70 def _on_zmq_reply(self, msg_list):
78 def _on_zmq_reply(self, msg_list):
71 """Handle a message the ZMQ stream sends to the router.
79 """Handle a message the ZMQ stream sends to the router.
72
80
73 Usually, this is where the return message will be written to
81 Usually, this is where the return message will be written to
74 clients that need it using client.write_message().
82 clients that need it using client.write_message().
75 """
83 """
76 pass
84 pass
77
85
78 def _reserialize_reply(self, msg_list):
86 def _reserialize_reply(self, msg_list):
79 """Reserialize a reply message using JSON.
87 """Reserialize a reply message using JSON.
80
88
81 This takes the msg list from the ZMQ socket, unserializes it using
89 This takes the msg list from the ZMQ socket, unserializes it using
82 self.session and then serializes the result using JSON. This method
90 self.session and then serializes the result using JSON. This method
83 should be used by self._on_zmq_reply to build messages that can
91 should be used by self._on_zmq_reply to build messages that can
84 be sent back to the browser.
92 be sent back to the browser.
85 """
93 """
86 idents, msg_list = self.session.feed_identities(msg_list)
94 idents, msg_list = self.session.feed_identities(msg_list)
87 msg = self.session.unserialize(msg_list)
95 msg = self.session.unserialize(msg_list)
88 msg['header'].pop('date')
96 msg['header'].pop('date')
89 msg.pop('buffers')
97 msg.pop('buffers')
90 return json.dumps(msg)
98 return json.dumps(msg)
91
99
92
100
93 class IOPubStreamRouter(ZMQStreamRouter):
101 class IOPubStreamRouter(ZMQStreamRouter):
94
102
95 def _on_zmq_reply(self, msg_list):
103 def _on_zmq_reply(self, msg_list):
96 msg = self._reserialize_reply(msg_list)
104 msg = self._reserialize_reply(msg_list)
97 for client_id, client in self._clients.items():
105 for client_id, client in self._clients.items():
98 client.write_message(msg)
106 client.write_message(msg)
99
107
100
108
101 class ShellStreamRouter(ZMQStreamRouter):
109 class ShellStreamRouter(ZMQStreamRouter):
102
110
103 _request_queue = Instance(Queue,(),{})
111 _request_queue = Instance(Queue,(),{})
104
112
105 def _on_zmq_reply(self, msg_list):
113 def _on_zmq_reply(self, msg_list):
106 msg = self._reserialize_reply(msg_list)
114 msg = self._reserialize_reply(msg_list)
107 client_id = self._request_queue.get(block=False)
115 client_id = self._request_queue.get(block=False)
108 client = self._clients.get(client_id)
116 client = self._clients.get(client_id)
109 if client is not None:
117 if client is not None:
110 client.write_message(msg)
118 client.write_message(msg)
111
119
112 def forward_msg(self, client_id, msg):
120 def forward_msg(self, client_id, msg):
113 if len(msg) < self.max_msg_size:
121 if len(msg) < self.max_msg_size:
114 msg = json.loads(msg)
122 msg = json.loads(msg)
115 # to_send = self.session.serialize(msg)
116 self._request_queue.put(client_id)
123 self._request_queue.put(client_id)
117 self.session.send(self.zmq_stream, msg)
124 self.session.send(self.zmq_stream, msg)
118
125
General Comments 0
You need to be logged in to leave comments. Login now