kernel.js 43 KB

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