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