##// END OF EJS Templates
Handle kernel messages synchronously...
Jason Grout -
Show More
@@ -1,217 +1,219 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 ], function(IPython, $, utils) {
9 9 "use strict";
10 10
11 11 //-----------------------------------------------------------------------
12 12 // CommManager class
13 13 //-----------------------------------------------------------------------
14 14
15 15 var CommManager = function (kernel) {
16 16 this.comms = {};
17 17 this.targets = {};
18 18 if (kernel !== undefined) {
19 19 this.init_kernel(kernel);
20 20 }
21 21 };
22 22
23 23 CommManager.prototype.init_kernel = function (kernel) {
24 24 /**
25 25 * connect the kernel, and register message handlers
26 26 */
27 27 this.kernel = kernel;
28 28 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
29 29 for (var i = 0; i < msg_types.length; i++) {
30 30 var msg_type = msg_types[i];
31 31 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
32 32 }
33 33 };
34 34
35 35 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
36 36 /**
37 37 * Create a new Comm, register it, and open its Kernel-side counterpart
38 38 * Mimics the auto-registration in `Comm.__init__` in the IPython Comm
39 39 */
40 40 var comm = new Comm(target_name);
41 41 this.register_comm(comm);
42 42 comm.open(data, callbacks, metadata);
43 43 return comm;
44 44 };
45 45
46 46 CommManager.prototype.register_target = function (target_name, f) {
47 47 /**
48 48 * Register a target function for a given target name
49 49 */
50 50 this.targets[target_name] = f;
51 51 };
52 52
53 53 CommManager.prototype.unregister_target = function (target_name, f) {
54 54 /**
55 55 * Unregister a target function for a given target name
56 56 */
57 57 delete this.targets[target_name];
58 58 };
59 59
60 60 CommManager.prototype.register_comm = function (comm) {
61 61 /**
62 62 * Register a comm in the mapping
63 63 */
64 64 this.comms[comm.comm_id] = Promise.resolve(comm);
65 65 comm.kernel = this.kernel;
66 66 return comm.comm_id;
67 67 };
68 68
69 69 CommManager.prototype.unregister_comm = function (comm) {
70 70 /**
71 71 * Remove a comm from the mapping
72 72 */
73 73 delete this.comms[comm.comm_id];
74 74 };
75 75
76 76 // comm message handlers
77 77
78 78 CommManager.prototype.comm_open = function (msg) {
79 79 var content = msg.content;
80 80 var that = this;
81 81 var comm_id = content.comm_id;
82 82
83 83 this.comms[comm_id] = utils.load_class(content.target_name, content.target_module,
84 84 this.targets).then(function(target) {
85 85 var comm = new Comm(content.target_name, comm_id);
86 86 comm.kernel = that.kernel;
87 87 try {
88 88 var response = target(comm, msg);
89 89 } catch (e) {
90 90 comm.close();
91 91 that.unregister_comm(comm);
92 92 var wrapped_error = new utils.WrappedError("Exception opening new comm", e);
93 93 console.error(wrapped_error);
94 94 return Promise.reject(wrapped_error);
95 95 }
96 96 // Regardless of the target return value, we need to
97 97 // then return the comm
98 98 return Promise.resolve(response).then(function() {return comm;});
99 99 }, utils.reject('Could not open comm', true));
100 100 return this.comms[comm_id];
101 101 };
102 102
103 103 CommManager.prototype.comm_close = function(msg) {
104 104 var content = msg.content;
105 105 if (this.comms[content.comm_id] === undefined) {
106 106 console.error('Comm promise not found for comm id ' + content.comm_id);
107 107 return;
108 108 }
109 109 var that = this;
110 110 this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
111 111 that.unregister_comm(comm);
112 112 try {
113 113 comm.handle_close(msg);
114 114 } catch (e) {
115 115 console.log("Exception closing comm: ", e, e.stack, msg);
116 116 }
117 117 // don't return a comm, so that further .then() functions
118 118 // get an undefined comm input
119 119 });
120 return this.comms[content.comm_id];
120 121 };
121 122
122 123 CommManager.prototype.comm_msg = function(msg) {
123 124 var content = msg.content;
124 125 if (this.comms[content.comm_id] === undefined) {
125 126 console.error('Comm promise not found for comm id ' + content.comm_id);
126 127 return;
127 128 }
128 129
129 130 this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
130 131 try {
131 132 comm.handle_msg(msg);
132 133 } catch (e) {
133 134 console.log("Exception handling comm msg: ", e, e.stack, msg);
134 135 }
135 136 return comm;
136 137 });
138 return this.comms[content.comm_id];
137 139 };
138 140
139 141 //-----------------------------------------------------------------------
140 142 // Comm base class
141 143 //-----------------------------------------------------------------------
142 144
143 145 var Comm = function (target_name, comm_id) {
144 146 this.target_name = target_name;
145 147 this.comm_id = comm_id || utils.uuid();
146 148 this._msg_callback = this._close_callback = null;
147 149 };
148 150
149 151 // methods for sending messages
150 152 Comm.prototype.open = function (data, callbacks, metadata) {
151 153 var content = {
152 154 comm_id : this.comm_id,
153 155 target_name : this.target_name,
154 156 data : data || {},
155 157 };
156 158 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
157 159 };
158 160
159 161 Comm.prototype.send = function (data, callbacks, metadata, buffers) {
160 162 var content = {
161 163 comm_id : this.comm_id,
162 164 data : data || {},
163 165 };
164 166 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata, buffers);
165 167 };
166 168
167 169 Comm.prototype.close = function (data, callbacks, metadata) {
168 170 var content = {
169 171 comm_id : this.comm_id,
170 172 data : data || {},
171 173 };
172 174 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
173 175 };
174 176
175 177 // methods for registering callbacks for incoming messages
176 178 Comm.prototype._register_callback = function (key, callback) {
177 179 this['_' + key + '_callback'] = callback;
178 180 };
179 181
180 182 Comm.prototype.on_msg = function (callback) {
181 183 this._register_callback('msg', callback);
182 184 };
183 185
184 186 Comm.prototype.on_close = function (callback) {
185 187 this._register_callback('close', callback);
186 188 };
187 189
188 190 // methods for handling incoming messages
189 191
190 192 Comm.prototype._callback = function (key, msg) {
191 193 var callback = this['_' + key + '_callback'];
192 194 if (callback) {
193 195 try {
194 196 callback(msg);
195 197 } catch (e) {
196 198 console.log("Exception in Comm callback", e, e.stack, msg);
197 199 }
198 200 }
199 201 };
200 202
201 203 Comm.prototype.handle_msg = function (msg) {
202 204 this._callback('msg', msg);
203 205 };
204 206
205 207 Comm.prototype.handle_close = function (msg) {
206 208 this._callback('close', msg);
207 209 };
208 210
209 211 // For backwards compatability.
210 212 IPython.CommManager = CommManager;
211 213 IPython.Comm = Comm;
212 214
213 215 return {
214 216 'CommManager': CommManager,
215 217 'Comm': Comm
216 218 };
217 219 });
@@ -1,1056 +1,1064 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 './comm',
9 9 './serialize',
10 10 'widgets/js/init'
11 11 ], function(IPython, $, utils, comm, serialize, widgetmanager) {
12 12 "use strict";
13 13
14 14 /**
15 15 * A Kernel class to communicate with the Python kernel. This
16 16 * should generally not be constructed directly, but be created
17 17 * by. the `Session` object. Once created, this object should be
18 18 * used to communicate with the kernel.
19 19 *
20 20 * @class Kernel
21 21 * @param {string} kernel_service_url - the URL to access the kernel REST api
22 22 * @param {string} ws_url - the websockets URL
23 23 * @param {Notebook} notebook - notebook object
24 24 * @param {string} name - the kernel type (e.g. python3)
25 25 */
26 26 var Kernel = function (kernel_service_url, ws_url, notebook, name) {
27 27 this.events = notebook.events;
28 28
29 29 this.id = null;
30 30 this.name = name;
31 31 this.ws = null;
32 32
33 33 this.kernel_service_url = kernel_service_url;
34 34 this.kernel_url = null;
35 35 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
36 36 if (!this.ws_url) {
37 37 // trailing 's' in https will become wss for secure web sockets
38 38 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
39 39 }
40 40
41 41 this.username = "username";
42 42 this.session_id = utils.uuid();
43 43 this._msg_callbacks = {};
44 44 this._msg_queue = Promise.resolve();
45 45 this.info_reply = {}; // kernel_info_reply stored here after starting
46 46
47 47 if (typeof(WebSocket) !== 'undefined') {
48 48 this.WebSocket = WebSocket;
49 49 } else if (typeof(MozWebSocket) !== 'undefined') {
50 50 this.WebSocket = MozWebSocket;
51 51 } else {
52 52 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
53 53 }
54 54
55 55 this.bind_events();
56 56 this.init_iopub_handlers();
57 57 this.comm_manager = new comm.CommManager(this);
58 58 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
59 59
60 60 this.last_msg_id = null;
61 61 this.last_msg_callbacks = {};
62 62
63 63 this._autorestart_attempt = 0;
64 64 this._reconnect_attempt = 0;
65 65 this.reconnect_limit = 7;
66 66 };
67 67
68 68 /**
69 69 * @function _get_msg
70 70 */
71 71 Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) {
72 72 var msg = {
73 73 header : {
74 74 msg_id : utils.uuid(),
75 75 username : this.username,
76 76 session : this.session_id,
77 77 msg_type : msg_type,
78 78 version : "5.0"
79 79 },
80 80 metadata : metadata || {},
81 81 content : content,
82 82 buffers : buffers || [],
83 83 parent_header : {}
84 84 };
85 85 return msg;
86 86 };
87 87
88 88 /**
89 89 * @function bind_events
90 90 */
91 91 Kernel.prototype.bind_events = function () {
92 92 var that = this;
93 93 this.events.on('send_input_reply.Kernel', function(evt, data) {
94 94 that.send_input_reply(data);
95 95 });
96 96
97 97 var record_status = function (evt, info) {
98 98 console.log('Kernel: ' + evt.type + ' (' + info.kernel.id + ')');
99 99 };
100 100
101 101 this.events.on('kernel_created.Kernel', record_status);
102 102 this.events.on('kernel_reconnecting.Kernel', record_status);
103 103 this.events.on('kernel_connected.Kernel', record_status);
104 104 this.events.on('kernel_starting.Kernel', record_status);
105 105 this.events.on('kernel_restarting.Kernel', record_status);
106 106 this.events.on('kernel_autorestarting.Kernel', record_status);
107 107 this.events.on('kernel_interrupting.Kernel', record_status);
108 108 this.events.on('kernel_disconnected.Kernel', record_status);
109 109 // these are commented out because they are triggered a lot, but can
110 110 // be uncommented for debugging purposes
111 111 //this.events.on('kernel_idle.Kernel', record_status);
112 112 //this.events.on('kernel_busy.Kernel', record_status);
113 113 this.events.on('kernel_ready.Kernel', record_status);
114 114 this.events.on('kernel_killed.Kernel', record_status);
115 115 this.events.on('kernel_dead.Kernel', record_status);
116 116
117 117 this.events.on('kernel_ready.Kernel', function () {
118 118 that._autorestart_attempt = 0;
119 119 });
120 120 this.events.on('kernel_connected.Kernel', function () {
121 121 that._reconnect_attempt = 0;
122 122 });
123 123 };
124 124
125 125 /**
126 126 * Initialize the iopub handlers.
127 127 *
128 128 * @function init_iopub_handlers
129 129 */
130 130 Kernel.prototype.init_iopub_handlers = function () {
131 131 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
132 132 this._iopub_handlers = {};
133 133 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
134 134 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
135 135 this.register_iopub_handler('execute_input', $.proxy(this._handle_input_message, this));
136 136
137 137 for (var i=0; i < output_msg_types.length; i++) {
138 138 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
139 139 }
140 140 };
141 141
142 142 /**
143 143 * GET /api/kernels
144 144 *
145 145 * Get the list of running kernels.
146 146 *
147 147 * @function list
148 148 * @param {function} [success] - function executed on ajax success
149 149 * @param {function} [error] - functon executed on ajax error
150 150 */
151 151 Kernel.prototype.list = function (success, error) {
152 152 $.ajax(this.kernel_service_url, {
153 153 processData: false,
154 154 cache: false,
155 155 type: "GET",
156 156 dataType: "json",
157 157 success: success,
158 158 error: this._on_error(error)
159 159 });
160 160 };
161 161
162 162 /**
163 163 * POST /api/kernels
164 164 *
165 165 * Start a new kernel.
166 166 *
167 167 * In general this shouldn't be used -- the kernel should be
168 168 * started through the session API. If you use this function and
169 169 * are also using the session API then your session and kernel
170 170 * WILL be out of sync!
171 171 *
172 172 * @function start
173 173 * @param {params} [Object] - parameters to include in the query string
174 174 * @param {function} [success] - function executed on ajax success
175 175 * @param {function} [error] - functon executed on ajax error
176 176 */
177 177 Kernel.prototype.start = function (params, success, error) {
178 178 var url = this.kernel_service_url;
179 179 var qs = $.param(params || {}); // query string for sage math stuff
180 180 if (qs !== "") {
181 181 url = url + "?" + qs;
182 182 }
183 183
184 184 this.events.trigger('kernel_starting.Kernel', {kernel: this});
185 185 var that = this;
186 186 var on_success = function (data, status, xhr) {
187 187 that.events.trigger('kernel_created.Kernel', {kernel: that});
188 188 that._kernel_created(data);
189 189 if (success) {
190 190 success(data, status, xhr);
191 191 }
192 192 };
193 193
194 194 $.ajax(url, {
195 195 processData: false,
196 196 cache: false,
197 197 type: "POST",
198 198 data: JSON.stringify({name: this.name}),
199 199 dataType: "json",
200 200 success: this._on_success(on_success),
201 201 error: this._on_error(error)
202 202 });
203 203
204 204 return url;
205 205 };
206 206
207 207 /**
208 208 * GET /api/kernels/[:kernel_id]
209 209 *
210 210 * Get information about the kernel.
211 211 *
212 212 * @function get_info
213 213 * @param {function} [success] - function executed on ajax success
214 214 * @param {function} [error] - functon executed on ajax error
215 215 */
216 216 Kernel.prototype.get_info = function (success, error) {
217 217 $.ajax(this.kernel_url, {
218 218 processData: false,
219 219 cache: false,
220 220 type: "GET",
221 221 dataType: "json",
222 222 success: this._on_success(success),
223 223 error: this._on_error(error)
224 224 });
225 225 };
226 226
227 227 /**
228 228 * DELETE /api/kernels/[:kernel_id]
229 229 *
230 230 * Shutdown the kernel.
231 231 *
232 232 * If you are also using sessions, then this function shoul NOT be
233 233 * used. Instead, use Session.delete. Otherwise, the session and
234 234 * kernel WILL be out of sync.
235 235 *
236 236 * @function kill
237 237 * @param {function} [success] - function executed on ajax success
238 238 * @param {function} [error] - functon executed on ajax error
239 239 */
240 240 Kernel.prototype.kill = function (success, error) {
241 241 this.events.trigger('kernel_killed.Kernel', {kernel: this});
242 242 this._kernel_dead();
243 243 $.ajax(this.kernel_url, {
244 244 processData: false,
245 245 cache: false,
246 246 type: "DELETE",
247 247 dataType: "json",
248 248 success: this._on_success(success),
249 249 error: this._on_error(error)
250 250 });
251 251 };
252 252
253 253 /**
254 254 * POST /api/kernels/[:kernel_id]/interrupt
255 255 *
256 256 * Interrupt the kernel.
257 257 *
258 258 * @function interrupt
259 259 * @param {function} [success] - function executed on ajax success
260 260 * @param {function} [error] - functon executed on ajax error
261 261 */
262 262 Kernel.prototype.interrupt = function (success, error) {
263 263 this.events.trigger('kernel_interrupting.Kernel', {kernel: this});
264 264
265 265 var that = this;
266 266 var on_success = function (data, status, xhr) {
267 267 /**
268 268 * get kernel info so we know what state the kernel is in
269 269 */
270 270 that.kernel_info();
271 271 if (success) {
272 272 success(data, status, xhr);
273 273 }
274 274 };
275 275
276 276 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
277 277 $.ajax(url, {
278 278 processData: false,
279 279 cache: false,
280 280 type: "POST",
281 281 dataType: "json",
282 282 success: this._on_success(on_success),
283 283 error: this._on_error(error)
284 284 });
285 285 };
286 286
287 287 Kernel.prototype.restart = function (success, error) {
288 288 /**
289 289 * POST /api/kernels/[:kernel_id]/restart
290 290 *
291 291 * Restart the kernel.
292 292 *
293 293 * @function interrupt
294 294 * @param {function} [success] - function executed on ajax success
295 295 * @param {function} [error] - functon executed on ajax error
296 296 */
297 297 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
298 298 this.stop_channels();
299 299
300 300 var that = this;
301 301 var on_success = function (data, status, xhr) {
302 302 that.events.trigger('kernel_created.Kernel', {kernel: that});
303 303 that._kernel_created(data);
304 304 if (success) {
305 305 success(data, status, xhr);
306 306 }
307 307 };
308 308
309 309 var on_error = function (xhr, status, err) {
310 310 that.events.trigger('kernel_dead.Kernel', {kernel: that});
311 311 that._kernel_dead();
312 312 if (error) {
313 313 error(xhr, status, err);
314 314 }
315 315 };
316 316
317 317 var url = utils.url_join_encode(this.kernel_url, 'restart');
318 318 $.ajax(url, {
319 319 processData: false,
320 320 cache: false,
321 321 type: "POST",
322 322 dataType: "json",
323 323 success: this._on_success(on_success),
324 324 error: this._on_error(on_error)
325 325 });
326 326 };
327 327
328 328 Kernel.prototype.reconnect = function () {
329 329 /**
330 330 * Reconnect to a disconnected kernel. This is not actually a
331 331 * standard HTTP request, but useful function nonetheless for
332 332 * reconnecting to the kernel if the connection is somehow lost.
333 333 *
334 334 * @function reconnect
335 335 */
336 336 if (this.is_connected()) {
337 337 return;
338 338 }
339 339 this._reconnect_attempt = this._reconnect_attempt + 1;
340 340 this.events.trigger('kernel_reconnecting.Kernel', {
341 341 kernel: this,
342 342 attempt: this._reconnect_attempt,
343 343 });
344 344 this.start_channels();
345 345 };
346 346
347 347 Kernel.prototype._on_success = function (success) {
348 348 /**
349 349 * Handle a successful AJAX request by updating the kernel id and
350 350 * name from the response, and then optionally calling a provided
351 351 * callback.
352 352 *
353 353 * @function _on_success
354 354 * @param {function} success - callback
355 355 */
356 356 var that = this;
357 357 return function (data, status, xhr) {
358 358 if (data) {
359 359 that.id = data.id;
360 360 that.name = data.name;
361 361 }
362 362 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
363 363 if (success) {
364 364 success(data, status, xhr);
365 365 }
366 366 };
367 367 };
368 368
369 369 Kernel.prototype._on_error = function (error) {
370 370 /**
371 371 * Handle a failed AJAX request by logging the error message, and
372 372 * then optionally calling a provided callback.
373 373 *
374 374 * @function _on_error
375 375 * @param {function} error - callback
376 376 */
377 377 return function (xhr, status, err) {
378 378 utils.log_ajax_error(xhr, status, err);
379 379 if (error) {
380 380 error(xhr, status, err);
381 381 }
382 382 };
383 383 };
384 384
385 385 Kernel.prototype._kernel_created = function (data) {
386 386 /**
387 387 * Perform necessary tasks once the kernel has been started,
388 388 * including actually connecting to the kernel.
389 389 *
390 390 * @function _kernel_created
391 391 * @param {Object} data - information about the kernel including id
392 392 */
393 393 this.id = data.id;
394 394 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
395 395 this.start_channels();
396 396 };
397 397
398 398 Kernel.prototype._kernel_connected = function () {
399 399 /**
400 400 * Perform necessary tasks once the connection to the kernel has
401 401 * been established. This includes requesting information about
402 402 * the kernel.
403 403 *
404 404 * @function _kernel_connected
405 405 */
406 406 this.events.trigger('kernel_connected.Kernel', {kernel: this});
407 407 // get kernel info so we know what state the kernel is in
408 408 var that = this;
409 409 this.kernel_info(function (reply) {
410 410 that.info_reply = reply.content;
411 411 that.events.trigger('kernel_ready.Kernel', {kernel: that});
412 412 });
413 413 };
414 414
415 415 Kernel.prototype._kernel_dead = function () {
416 416 /**
417 417 * Perform necessary tasks after the kernel has died. This closing
418 418 * communication channels to the kernel if they are still somehow
419 419 * open.
420 420 *
421 421 * @function _kernel_dead
422 422 */
423 423 this.stop_channels();
424 424 };
425 425
426 426 Kernel.prototype.start_channels = function () {
427 427 /**
428 428 * Start the websocket channels.
429 429 * Will stop and restart them if they already exist.
430 430 *
431 431 * @function start_channels
432 432 */
433 433 var that = this;
434 434 this.stop_channels();
435 435 var ws_host_url = this.ws_url + this.kernel_url;
436 436
437 437 console.log("Starting WebSockets:", ws_host_url);
438 438
439 439 this.ws = new this.WebSocket([
440 440 that.ws_url,
441 441 utils.url_join_encode(that.kernel_url, 'channels'),
442 442 "?session_id=" + that.session_id
443 443 ].join('')
444 444 );
445 445
446 446 var already_called_onclose = false; // only alert once
447 447 var ws_closed_early = function(evt){
448 448 if (already_called_onclose){
449 449 return;
450 450 }
451 451 already_called_onclose = true;
452 452 if ( ! evt.wasClean ){
453 453 // If the websocket was closed early, that could mean
454 454 // that the kernel is actually dead. Try getting
455 455 // information about the kernel from the API call --
456 456 // if that fails, then assume the kernel is dead,
457 457 // otherwise just follow the typical websocket closed
458 458 // protocol.
459 459 that.get_info(function () {
460 460 that._ws_closed(ws_host_url, false);
461 461 }, function () {
462 462 that.events.trigger('kernel_dead.Kernel', {kernel: that});
463 463 that._kernel_dead();
464 464 });
465 465 }
466 466 };
467 467 var ws_closed_late = function(evt){
468 468 if (already_called_onclose){
469 469 return;
470 470 }
471 471 already_called_onclose = true;
472 472 if ( ! evt.wasClean ){
473 473 that._ws_closed(ws_host_url, false);
474 474 }
475 475 };
476 476 var ws_error = function(evt){
477 477 if (already_called_onclose){
478 478 return;
479 479 }
480 480 already_called_onclose = true;
481 481 that._ws_closed(ws_host_url, true);
482 482 };
483 483
484 484 this.ws.onopen = $.proxy(this._ws_opened, this);
485 485 this.ws.onclose = ws_closed_early;
486 486 this.ws.onerror = ws_error;
487 487 // switch from early-close to late-close message after 1s
488 488 setTimeout(function() {
489 489 if (that.ws !== null) {
490 490 that.ws.onclose = ws_closed_late;
491 491 }
492 492 }, 1000);
493 493 this.ws.onmessage = $.proxy(this._handle_ws_message, this);
494 494 };
495 495
496 496 Kernel.prototype._ws_opened = function (evt) {
497 497 /**
498 498 * Handle a websocket entering the open state,
499 499 * signaling that the kernel is connected when websocket is open.
500 500 *
501 501 * @function _ws_opened
502 502 */
503 503 if (this.is_connected()) {
504 504 // all events ready, trigger started event.
505 505 this._kernel_connected();
506 506 }
507 507 };
508 508
509 509 Kernel.prototype._ws_closed = function(ws_url, error) {
510 510 /**
511 511 * Handle a websocket entering the closed state. If the websocket
512 512 * was not closed due to an error, try to reconnect to the kernel.
513 513 *
514 514 * @function _ws_closed
515 515 * @param {string} ws_url - the websocket url
516 516 * @param {bool} error - whether the connection was closed due to an error
517 517 */
518 518 this.stop_channels();
519 519
520 520 this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
521 521 if (error) {
522 522 console.log('WebSocket connection failed: ', ws_url);
523 523 this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
524 524 }
525 525 this._schedule_reconnect();
526 526 };
527 527
528 528 Kernel.prototype._schedule_reconnect = function () {
529 529 /**
530 530 * function to call when kernel connection is lost
531 531 * schedules reconnect, or fires 'connection_dead' if reconnect limit is hit
532 532 */
533 533 if (this._reconnect_attempt < this.reconnect_limit) {
534 534 var timeout = Math.pow(2, this._reconnect_attempt);
535 535 console.log("Connection lost, reconnecting in " + timeout + " seconds.");
536 536 setTimeout($.proxy(this.reconnect, this), 1e3 * timeout);
537 537 } else {
538 538 this.events.trigger('kernel_connection_dead.Kernel', {
539 539 kernel: this,
540 540 reconnect_attempt: this._reconnect_attempt,
541 541 });
542 542 console.log("Failed to reconnect, giving up.");
543 543 }
544 544 };
545 545
546 546 Kernel.prototype.stop_channels = function () {
547 547 /**
548 548 * Close the websocket. After successful close, the value
549 549 * in `this.ws` will be null.
550 550 *
551 551 * @function stop_channels
552 552 */
553 553 var that = this;
554 554 var close = function () {
555 555 if (that.ws && that.ws.readyState === WebSocket.CLOSED) {
556 556 that.ws = null;
557 557 }
558 558 };
559 559 if (this.ws !== null) {
560 560 if (this.ws.readyState === WebSocket.OPEN) {
561 561 this.ws.onclose = close;
562 562 this.ws.close();
563 563 } else {
564 564 close();
565 565 }
566 566 }
567 567 };
568 568
569 569 Kernel.prototype.is_connected = function () {
570 570 /**
571 571 * Check whether there is a connection to the kernel. This
572 572 * function only returns true if websocket has been
573 573 * created and has a state of WebSocket.OPEN.
574 574 *
575 575 * @function is_connected
576 576 * @returns {bool} - whether there is a connection
577 577 */
578 578 // if any channel is not ready, then we're not connected
579 579 if (this.ws === null) {
580 580 return false;
581 581 }
582 582 if (this.ws.readyState !== WebSocket.OPEN) {
583 583 return false;
584 584 }
585 585 return true;
586 586 };
587 587
588 588 Kernel.prototype.is_fully_disconnected = function () {
589 589 /**
590 590 * Check whether the connection to the kernel has been completely
591 591 * severed. This function only returns true if all channel objects
592 592 * are null.
593 593 *
594 594 * @function is_fully_disconnected
595 595 * @returns {bool} - whether the kernel is fully disconnected
596 596 */
597 597 return (this.ws === null);
598 598 };
599 599
600 600 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
601 601 /**
602 602 * Send a message on the Kernel's shell channel
603 603 *
604 604 * @function send_shell_message
605 605 */
606 606 if (!this.is_connected()) {
607 607 throw new Error("kernel is not connected");
608 608 }
609 609 var msg = this._get_msg(msg_type, content, metadata, buffers);
610 610 msg.channel = 'shell';
611 611 this.ws.send(serialize.serialize(msg));
612 612 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
613 613 return msg.header.msg_id;
614 614 };
615 615
616 616 Kernel.prototype.kernel_info = function (callback) {
617 617 /**
618 618 * Get kernel info
619 619 *
620 620 * @function kernel_info
621 621 * @param callback {function}
622 622 *
623 623 * When calling this method, pass a callback function that expects one argument.
624 624 * The callback will be passed the complete `kernel_info_reply` message documented
625 625 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
626 626 */
627 627 var callbacks;
628 628 if (callback) {
629 629 callbacks = { shell : { reply : callback } };
630 630 }
631 631 return this.send_shell_message("kernel_info_request", {}, callbacks);
632 632 };
633 633
634 634 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
635 635 /**
636 636 * Get info on an object
637 637 *
638 638 * When calling this method, pass a callback function that expects one argument.
639 639 * The callback will be passed the complete `inspect_reply` message documented
640 640 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
641 641 *
642 642 * @function inspect
643 643 * @param code {string}
644 644 * @param cursor_pos {integer}
645 645 * @param callback {function}
646 646 */
647 647 var callbacks;
648 648 if (callback) {
649 649 callbacks = { shell : { reply : callback } };
650 650 }
651 651
652 652 var content = {
653 653 code : code,
654 654 cursor_pos : cursor_pos,
655 655 detail_level : 0
656 656 };
657 657 return this.send_shell_message("inspect_request", content, callbacks);
658 658 };
659 659
660 660 Kernel.prototype.execute = function (code, callbacks, options) {
661 661 /**
662 662 * Execute given code into kernel, and pass result to callback.
663 663 *
664 664 * @async
665 665 * @function execute
666 666 * @param {string} code
667 667 * @param [callbacks] {Object} With the following keys (all optional)
668 668 * @param callbacks.shell.reply {function}
669 669 * @param callbacks.shell.payload.[payload_name] {function}
670 670 * @param callbacks.iopub.output {function}
671 671 * @param callbacks.iopub.clear_output {function}
672 672 * @param callbacks.input {function}
673 673 * @param {object} [options]
674 674 * @param [options.silent=false] {Boolean}
675 675 * @param [options.user_expressions=empty_dict] {Dict}
676 676 * @param [options.allow_stdin=false] {Boolean} true|false
677 677 *
678 678 * @example
679 679 *
680 680 * The options object should contain the options for the execute
681 681 * call. Its default values are:
682 682 *
683 683 * options = {
684 684 * silent : true,
685 685 * user_expressions : {},
686 686 * allow_stdin : false
687 687 * }
688 688 *
689 689 * When calling this method pass a callbacks structure of the
690 690 * form:
691 691 *
692 692 * callbacks = {
693 693 * shell : {
694 694 * reply : execute_reply_callback,
695 695 * payload : {
696 696 * set_next_input : set_next_input_callback,
697 697 * }
698 698 * },
699 699 * iopub : {
700 700 * output : output_callback,
701 701 * clear_output : clear_output_callback,
702 702 * },
703 703 * input : raw_input_callback
704 704 * }
705 705 *
706 706 * Each callback will be passed the entire message as a single
707 707 * arugment. Payload handlers will be passed the corresponding
708 708 * payload and the execute_reply message.
709 709 */
710 710 var content = {
711 711 code : code,
712 712 silent : true,
713 713 store_history : false,
714 714 user_expressions : {},
715 715 allow_stdin : false
716 716 };
717 717 callbacks = callbacks || {};
718 718 if (callbacks.input !== undefined) {
719 719 content.allow_stdin = true;
720 720 }
721 721 $.extend(true, content, options);
722 722 this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
723 723 return this.send_shell_message("execute_request", content, callbacks);
724 724 };
725 725
726 726 /**
727 727 * When calling this method, pass a function to be called with the
728 728 * `complete_reply` message as its only argument when it arrives.
729 729 *
730 730 * `complete_reply` is documented
731 731 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
732 732 *
733 733 * @function complete
734 734 * @param code {string}
735 735 * @param cursor_pos {integer}
736 736 * @param callback {function}
737 737 */
738 738 Kernel.prototype.complete = function (code, cursor_pos, callback) {
739 739 var callbacks;
740 740 if (callback) {
741 741 callbacks = { shell : { reply : callback } };
742 742 }
743 743 var content = {
744 744 code : code,
745 745 cursor_pos : cursor_pos
746 746 };
747 747 return this.send_shell_message("complete_request", content, callbacks);
748 748 };
749 749
750 750 /**
751 751 * @function send_input_reply
752 752 */
753 753 Kernel.prototype.send_input_reply = function (input) {
754 754 if (!this.is_connected()) {
755 755 throw new Error("kernel is not connected");
756 756 }
757 757 var content = {
758 758 value : input
759 759 };
760 760 this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
761 761 var msg = this._get_msg("input_reply", content);
762 762 msg.channel = 'stdin';
763 763 this.ws.send(serialize.serialize(msg));
764 764 return msg.header.msg_id;
765 765 };
766 766
767 767 /**
768 768 * @function register_iopub_handler
769 769 */
770 770 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
771 771 this._iopub_handlers[msg_type] = callback;
772 772 };
773 773
774 774 /**
775 775 * Get the iopub handler for a specific message type.
776 776 *
777 777 * @function get_iopub_handler
778 778 */
779 779 Kernel.prototype.get_iopub_handler = function (msg_type) {
780 780 return this._iopub_handlers[msg_type];
781 781 };
782 782
783 783 /**
784 784 * Get callbacks for a specific message.
785 785 *
786 786 * @function get_callbacks_for_msg
787 787 */
788 788 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
789 789 if (msg_id == this.last_msg_id) {
790 790 return this.last_msg_callbacks;
791 791 } else {
792 792 return this._msg_callbacks[msg_id];
793 793 }
794 794 };
795 795
796 796 /**
797 797 * Clear callbacks for a specific message.
798 798 *
799 799 * @function clear_callbacks_for_msg
800 800 */
801 801 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
802 802 if (this._msg_callbacks[msg_id] !== undefined ) {
803 803 delete this._msg_callbacks[msg_id];
804 804 }
805 805 };
806 806
807 807 /**
808 808 * @function _finish_shell
809 809 */
810 810 Kernel.prototype._finish_shell = function (msg_id) {
811 811 var callbacks = this._msg_callbacks[msg_id];
812 812 if (callbacks !== undefined) {
813 813 callbacks.shell_done = true;
814 814 if (callbacks.iopub_done) {
815 815 this.clear_callbacks_for_msg(msg_id);
816 816 }
817 817 }
818 818 };
819 819
820 820 /**
821 821 * @function _finish_iopub
822 822 */
823 823 Kernel.prototype._finish_iopub = function (msg_id) {
824 824 var callbacks = this._msg_callbacks[msg_id];
825 825 if (callbacks !== undefined) {
826 826 callbacks.iopub_done = true;
827 827 if (callbacks.shell_done) {
828 828 this.clear_callbacks_for_msg(msg_id);
829 829 }
830 830 }
831 831 };
832 832
833 833 /**
834 834 * Set callbacks for a particular message.
835 835 * Callbacks should be a struct of the following form:
836 836 * shell : {
837 837 *
838 838 * }
839 839 *
840 840 * @function set_callbacks_for_msg
841 841 */
842 842 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
843 843 this.last_msg_id = msg_id;
844 844 if (callbacks) {
845 845 // shallow-copy mapping, because we will modify it at the top level
846 846 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
847 847 cbcopy.shell = callbacks.shell;
848 848 cbcopy.iopub = callbacks.iopub;
849 849 cbcopy.input = callbacks.input;
850 850 cbcopy.shell_done = (!callbacks.shell);
851 851 cbcopy.iopub_done = (!callbacks.iopub);
852 852 } else {
853 853 this.last_msg_callbacks = {};
854 854 }
855 855 };
856 856
857 857 Kernel.prototype._handle_ws_message = function (e) {
858 var that = this;
858 859 this._msg_queue = this._msg_queue.then(function() {
859 860 return serialize.deserialize(e.data);
860 }).then($.proxy(this._finish_ws_message, this))
861 }).then(function(msg) {return that._finish_ws_message(msg);})
861 862 .catch(utils.reject("Couldn't process kernel message", true));
862 863 };
863 864
864 865 Kernel.prototype._finish_ws_message = function (msg) {
865 866 switch (msg.channel) {
866 867 case 'shell':
867 this._handle_shell_reply(msg);
868 return this._handle_shell_reply(msg);
868 869 break;
869 870 case 'iopub':
870 this._handle_iopub_message(msg);
871 return this._handle_iopub_message(msg);
871 872 break;
872 873 case 'stdin':
873 this._handle_input_request(msg);
874 return this._handle_input_request(msg);
874 875 break;
875 876 default:
876 877 console.error("unrecognized message channel", msg.channel, msg);
877 878 }
878 879 };
879 880
880 881 Kernel.prototype._handle_shell_reply = function (reply) {
881 882 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
883 var that = this;
882 884 var content = reply.content;
883 885 var metadata = reply.metadata;
884 886 var parent_id = reply.parent_header.msg_id;
885 887 var callbacks = this.get_callbacks_for_msg(parent_id);
888 var promise = Promise.resolve();
886 889 if (!callbacks || !callbacks.shell) {
887 890 return;
888 891 }
889 892 var shell_callbacks = callbacks.shell;
890 893
891 894 // signal that shell callbacks are done
892 895 this._finish_shell(parent_id);
893 896
894 897 if (shell_callbacks.reply !== undefined) {
895 shell_callbacks.reply(reply);
898 promise = promise.then(function() {return shell_callbacks.reply(reply)});
896 899 }
897 900 if (content.payload && shell_callbacks.payload) {
898 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
901 promise = promise.then(function() {
902 return that._handle_payloads(content.payload, shell_callbacks.payload, reply);
903 });
899 904 }
905 return promise;
900 906 };
901 907
902 908 /**
903 909 * @function _handle_payloads
904 910 */
905 911 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
912 var promise = [];
906 913 var l = payloads.length;
907 914 // Payloads are handled by triggering events because we don't want the Kernel
908 915 // to depend on the Notebook or Pager classes.
909 916 for (var i=0; i<l; i++) {
910 917 var payload = payloads[i];
911 918 var callback = payload_callbacks[payload.source];
912 919 if (callback) {
913 callback(payload, msg);
920 promise.push(callback(payload, msg));
914 921 }
915 922 }
923 return Promise.all(promise);
916 924 };
917 925
918 926 /**
919 927 * @function _handle_status_message
920 928 */
921 929 Kernel.prototype._handle_status_message = function (msg) {
922 930 var execution_state = msg.content.execution_state;
923 931 var parent_id = msg.parent_header.msg_id;
924 932
925 933 // dispatch status msg callbacks, if any
926 934 var callbacks = this.get_callbacks_for_msg(parent_id);
927 935 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
928 936 try {
929 937 callbacks.iopub.status(msg);
930 938 } catch (e) {
931 939 console.log("Exception in status msg handler", e, e.stack);
932 940 }
933 941 }
934 942
935 943 if (execution_state === 'busy') {
936 944 this.events.trigger('kernel_busy.Kernel', {kernel: this});
937 945
938 946 } else if (execution_state === 'idle') {
939 947 // signal that iopub callbacks are (probably) done
940 948 // async output may still arrive,
941 949 // but only for the most recent request
942 950 this._finish_iopub(parent_id);
943 951
944 952 // trigger status_idle event
945 953 this.events.trigger('kernel_idle.Kernel', {kernel: this});
946 954
947 955 } else if (execution_state === 'starting') {
948 956 this.events.trigger('kernel_starting.Kernel', {kernel: this});
949 957 var that = this;
950 958 this.kernel_info(function (reply) {
951 959 that.info_reply = reply.content;
952 960 that.events.trigger('kernel_ready.Kernel', {kernel: that});
953 961 });
954 962
955 963 } else if (execution_state === 'restarting') {
956 964 // autorestarting is distinct from restarting,
957 965 // in that it means the kernel died and the server is restarting it.
958 966 // kernel_restarting sets the notification widget,
959 967 // autorestart shows the more prominent dialog.
960 968 this._autorestart_attempt = this._autorestart_attempt + 1;
961 969 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
962 970 this.events.trigger('kernel_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt});
963 971
964 972 } else if (execution_state === 'dead') {
965 973 this.events.trigger('kernel_dead.Kernel', {kernel: this});
966 974 this._kernel_dead();
967 975 }
968 976 };
969 977
970 978 /**
971 979 * Handle clear_output message
972 980 *
973 981 * @function _handle_clear_output
974 982 */
975 983 Kernel.prototype._handle_clear_output = function (msg) {
976 984 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
977 985 if (!callbacks || !callbacks.iopub) {
978 986 return;
979 987 }
980 988 var callback = callbacks.iopub.clear_output;
981 989 if (callback) {
982 990 callback(msg);
983 991 }
984 992 };
985 993
986 994 /**
987 995 * handle an output message (execute_result, display_data, etc.)
988 996 *
989 997 * @function _handle_output_message
990 998 */
991 999 Kernel.prototype._handle_output_message = function (msg) {
992 1000 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
993 1001 if (!callbacks || !callbacks.iopub) {
994 1002 // The message came from another client. Let the UI decide what to
995 1003 // do with it.
996 1004 this.events.trigger('received_unsolicited_message.Kernel', msg);
997 1005 return;
998 1006 }
999 1007 var callback = callbacks.iopub.output;
1000 1008 if (callback) {
1001 1009 callback(msg);
1002 1010 }
1003 1011 };
1004 1012
1005 1013 /**
1006 1014 * Handle an input message (execute_input).
1007 1015 *
1008 1016 * @function _handle_input message
1009 1017 */
1010 1018 Kernel.prototype._handle_input_message = function (msg) {
1011 1019 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
1012 1020 if (!callbacks) {
1013 1021 // The message came from another client. Let the UI decide what to
1014 1022 // do with it.
1015 1023 this.events.trigger('received_unsolicited_message.Kernel', msg);
1016 1024 }
1017 1025 };
1018 1026
1019 1027 /**
1020 1028 * Dispatch IOPub messages to respective handlers. Each message
1021 1029 * type should have a handler.
1022 1030 *
1023 1031 * @function _handle_iopub_message
1024 1032 */
1025 1033 Kernel.prototype._handle_iopub_message = function (msg) {
1026 1034 var handler = this.get_iopub_handler(msg.header.msg_type);
1027 1035 if (handler !== undefined) {
1028 handler(msg);
1036 return handler(msg);
1029 1037 }
1030 1038 };
1031 1039
1032 1040 /**
1033 1041 * @function _handle_input_request
1034 1042 */
1035 1043 Kernel.prototype._handle_input_request = function (request) {
1036 1044 var header = request.header;
1037 1045 var content = request.content;
1038 1046 var metadata = request.metadata;
1039 1047 var msg_type = header.msg_type;
1040 1048 if (msg_type !== 'input_request') {
1041 1049 console.log("Invalid input request!", request);
1042 1050 return;
1043 1051 }
1044 1052 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
1045 1053 if (callbacks) {
1046 1054 if (callbacks.input) {
1047 1055 callbacks.input(request);
1048 1056 }
1049 1057 }
1050 1058 };
1051 1059
1052 1060 // Backwards compatability.
1053 1061 IPython.Kernel = Kernel;
1054 1062
1055 1063 return {'Kernel': Kernel};
1056 1064 });
General Comments 0
You need to be logged in to leave comments. Login now