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