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