default.ts 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. URLExt, uuid
  5. } from '@jupyterlab/coreutils';
  6. import {
  7. ArrayExt, each, find
  8. } from '@phosphor/algorithm';
  9. import {
  10. JSONExt, JSONObject, PromiseDelegate
  11. } from '@phosphor/coreutils';
  12. import {
  13. ISignal, Signal
  14. } from '@phosphor/signaling';
  15. import {
  16. ServerConnection
  17. } from '..';
  18. import {
  19. CommHandler
  20. } from './comm';
  21. import {
  22. Kernel, log
  23. } from './kernel';
  24. import {
  25. KernelMessage
  26. } from './messages';
  27. import {
  28. KernelFutureHandler
  29. } from './future';
  30. import * as serialize
  31. from './serialize';
  32. import * as validate
  33. from './validate';
  34. /**
  35. * The url for the kernel service.
  36. */
  37. const KERNEL_SERVICE_URL = 'api/kernels';
  38. /**
  39. * The url for the kernelspec service.
  40. */
  41. const KERNELSPEC_SERVICE_URL = 'api/kernelspecs';
  42. // Stub for requirejs.
  43. declare var requirejs: any;
  44. /**
  45. * Implementation of the Kernel object.
  46. *
  47. * #### Notes
  48. * Messages from the server are handled in the order they were received and
  49. * asynchronously. Any message handler can return a promise, and message
  50. * handling will pause until the promise is fulfilled.
  51. */
  52. export
  53. class DefaultKernel implements Kernel.IKernel {
  54. /**
  55. * Construct a kernel object.
  56. */
  57. constructor(options: Kernel.IOptions, id: string) {
  58. this._name = options.name;
  59. this._id = id;
  60. this.serverSettings = options.serverSettings || ServerConnection.makeSettings();
  61. this._clientId = options.clientId || uuid();
  62. this._username = options.username || '';
  63. this._futures = new Map<string, KernelFutureHandler>();
  64. this._comms = new Map<string, Kernel.IComm>();
  65. this._createSocket();
  66. Private.runningKernels.push(this);
  67. }
  68. /**
  69. * A signal emitted when the kernel is shut down.
  70. */
  71. get terminated(): ISignal<this, void> {
  72. return this._terminated;
  73. }
  74. /**
  75. * The server settings for the kernel.
  76. */
  77. readonly serverSettings: ServerConnection.ISettings;
  78. /**
  79. * A signal emitted when the kernel status changes.
  80. */
  81. get statusChanged(): ISignal<this, Kernel.Status> {
  82. return this._statusChanged;
  83. }
  84. /**
  85. * A signal emitted for iopub kernel messages.
  86. *
  87. * #### Notes
  88. * This signal is emitted after the iopub message is handled asynchronously.
  89. */
  90. get iopubMessage(): ISignal<this, KernelMessage.IIOPubMessage> {
  91. return this._iopubMessage;
  92. }
  93. /**
  94. * A signal emitted for unhandled kernel message.
  95. *
  96. * #### Notes
  97. * This signal is emitted for a message that was not handled.
  98. */
  99. get unhandledMessage(): ISignal<this, KernelMessage.IMessage> {
  100. return this._unhandledMessage;
  101. }
  102. /**
  103. * A signal emitted for any kernel message.
  104. *
  105. * #### Notes
  106. * The behavior is undefined if the message is modified during message
  107. * handling. As such, the message should be treated as read-only.
  108. */
  109. get anyMessage(): ISignal<this, Kernel.IAnyMessageArgs> {
  110. return this._anyMessage;
  111. }
  112. /**
  113. * The id of the server-side kernel.
  114. */
  115. get id(): string {
  116. return this._id;
  117. }
  118. /**
  119. * The name of the server-side kernel.
  120. */
  121. get name(): string {
  122. return this._name;
  123. }
  124. /**
  125. * Get the model associated with the kernel.
  126. */
  127. get model(): Kernel.IModel {
  128. return { name: this.name, id: this.id };
  129. }
  130. /**
  131. * The client username.
  132. */
  133. get username(): string {
  134. return this._username;
  135. }
  136. /**
  137. * The client unique id.
  138. */
  139. get clientId(): string {
  140. return this._clientId;
  141. }
  142. /**
  143. * The current status of the kernel.
  144. */
  145. get status(): Kernel.Status {
  146. return this._status;
  147. }
  148. /**
  149. * Test whether the kernel has been disposed.
  150. */
  151. get isDisposed(): boolean {
  152. return this._isDisposed;
  153. }
  154. /**
  155. * The cached kernel info.
  156. *
  157. * #### Notes
  158. * This value will be null until the kernel is ready.
  159. */
  160. get info(): KernelMessage.IInfoReply | null {
  161. return this._info;
  162. }
  163. /**
  164. * Test whether the kernel is ready.
  165. */
  166. get isReady(): boolean {
  167. return this._isReady;
  168. }
  169. /**
  170. * A promise that is fulfilled when the kernel is ready.
  171. */
  172. get ready(): Promise<void> {
  173. return this._connectionPromise.promise;
  174. }
  175. /**
  176. * Get the kernel spec.
  177. *
  178. * @returns A promise that resolves with the kernel spec.
  179. */
  180. getSpec(): Promise<Kernel.ISpecModel> {
  181. if (this._specPromise) {
  182. return this._specPromise;
  183. }
  184. this._specPromise = Private.findSpecs(this.serverSettings).then(specs => {
  185. return specs.kernelspecs[this._name];
  186. });
  187. return this._specPromise;
  188. }
  189. /**
  190. * Clone the current kernel with a new clientId.
  191. */
  192. clone(): Kernel.IKernel {
  193. return new DefaultKernel({
  194. name: this._name,
  195. username: this._username,
  196. serverSettings: this.serverSettings
  197. }, this._id);
  198. }
  199. /**
  200. * Dispose of the resources held by the kernel.
  201. */
  202. dispose(): void {
  203. if (this.isDisposed) {
  204. return;
  205. }
  206. this._isDisposed = true;
  207. this._terminated.emit(void 0);
  208. this._status = 'dead';
  209. this._clearSocket();
  210. this._futures.forEach(future => { future.dispose(); });
  211. this._comms.forEach(comm => { comm.dispose(); });
  212. this._kernelSession = '';
  213. this._msgChain = null;
  214. this._displayIdToParentIds.clear();
  215. this._msgIdToDisplayIds.clear();
  216. ArrayExt.removeFirstOf(Private.runningKernels, this);
  217. Signal.clearData(this);
  218. }
  219. /**
  220. * Send a shell message to the kernel.
  221. *
  222. * #### Notes
  223. * Send a message to the kernel's shell channel, yielding a future object
  224. * for accepting replies.
  225. *
  226. * If `expectReply` is given and `true`, the future is disposed when both a
  227. * shell reply and an idle status message are received. If `expectReply`
  228. * is not given or is `false`, the future is resolved when an idle status
  229. * message is received.
  230. * If `disposeOnDone` is not given or is `true`, the Future is disposed at this point.
  231. * If `disposeOnDone` is given and `false`, it is up to the caller to dispose of the Future.
  232. *
  233. * All replies are validated as valid kernel messages.
  234. *
  235. * If the kernel status is `dead`, this will throw an error.
  236. */
  237. sendShellMessage(msg: KernelMessage.IShellMessage, expectReply=false, disposeOnDone=true): Kernel.IFuture {
  238. if (this.status === 'dead') {
  239. throw new Error('Kernel is dead');
  240. }
  241. if (!this._isReady || !this._ws) {
  242. this._pendingMessages.push(msg);
  243. } else {
  244. let msgType = msg.header.msg_type;
  245. if (msgType === 'status') {
  246. msgType += ' ' + (msg.content as any).execution_state;
  247. }
  248. log(`JS KERNEL SENT MESSAGE: K${this.id.slice(0, 6)} M${msg.header.msg_id.slice(0, 6)} ${msgType}`);
  249. this._ws.send(serialize.serialize(msg));
  250. }
  251. this._anyMessage.emit({msg, direction: 'send'});
  252. let future = new KernelFutureHandler(() => {
  253. let msgId = msg.header.msg_id;
  254. this._futures.delete(msgId);
  255. // Remove stored display id information.
  256. let displayIds = this._msgIdToDisplayIds.get(msgId);
  257. if (!displayIds) {
  258. return;
  259. }
  260. displayIds.forEach(displayId => {
  261. let msgIds = this._displayIdToParentIds.get(displayId);
  262. if (msgIds) {
  263. let idx = msgIds.indexOf(msgId);
  264. if (idx === -1) {
  265. return;
  266. }
  267. if (msgIds.length === 1) {
  268. this._displayIdToParentIds.delete(displayId);
  269. } else {
  270. msgIds.splice(idx, 1);
  271. this._displayIdToParentIds.set(displayId, msgIds);
  272. }
  273. }
  274. });
  275. this._msgIdToDisplayIds.delete(msgId);
  276. }, msg, expectReply, disposeOnDone, this);
  277. this._futures.set(msg.header.msg_id, future);
  278. return future;
  279. }
  280. /**
  281. * Interrupt a kernel.
  282. *
  283. * #### Notes
  284. * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels).
  285. *
  286. * The promise is fulfilled on a valid response and rejected otherwise.
  287. *
  288. * It is assumed that the API call does not mutate the kernel id or name.
  289. *
  290. * The promise will be rejected if the kernel status is `Dead` or if the
  291. * request fails or the response is invalid.
  292. */
  293. interrupt(): Promise<void> {
  294. return Private.interruptKernel(this, this.serverSettings);
  295. }
  296. /**
  297. * Restart a kernel.
  298. *
  299. * #### Notes
  300. * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels) and validates the response model.
  301. *
  302. * Any existing Future or Comm objects are cleared.
  303. *
  304. * The promise is fulfilled on a valid response and rejected otherwise.
  305. *
  306. * It is assumed that the API call does not mutate the kernel id or name.
  307. *
  308. * The promise will be rejected if the request fails or the response is
  309. * invalid.
  310. */
  311. restart(): Promise<void> {
  312. return Private.restartKernel(this, this.serverSettings);
  313. }
  314. /**
  315. * Handle a restart on the kernel. This is not part of the `IKernel`
  316. * interface.
  317. */
  318. handleRestart(): void {
  319. this._clearState();
  320. this._updateStatus('restarting');
  321. this._clearSocket();
  322. }
  323. /**
  324. * Reconnect to a disconnected kernel.
  325. *
  326. * #### Notes
  327. * Used when the websocket connection to the kernel is lost.
  328. */
  329. reconnect(): Promise<void> {
  330. this._clearSocket();
  331. this._updateStatus('reconnecting');
  332. this._createSocket();
  333. return this._connectionPromise.promise;
  334. }
  335. /**
  336. * Shutdown a kernel.
  337. *
  338. * #### Notes
  339. * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels).
  340. *
  341. * The promise is fulfilled on a valid response and rejected otherwise.
  342. *
  343. * On a valid response, closes the websocket and disposes of the kernel
  344. * object, and fulfills the promise.
  345. *
  346. * The promise will be rejected if the kernel status is `Dead` or if the
  347. * request fails or the response is invalid.
  348. */
  349. shutdown(): Promise<void> {
  350. if (this.status === 'dead') {
  351. return Promise.reject(new Error('Kernel is dead'));
  352. }
  353. return Private.shutdownKernel(this.id, this.serverSettings).then(() => {
  354. this._clearState();
  355. this._clearSocket();
  356. });
  357. }
  358. /**
  359. * Send a `kernel_info_request` message.
  360. *
  361. * #### Notes
  362. * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-info).
  363. *
  364. * Fulfills with the `kernel_info_response` content when the shell reply is
  365. * received and validated.
  366. *
  367. * TODO: this should be automatically run every time our kernel restarts,
  368. * before we say the kernel is ready, and cache the info and the kernel
  369. * session id. Further calls to this should returned the cached results.
  370. */
  371. async requestKernelInfo(): Promise<KernelMessage.IInfoReplyMsg> {
  372. let options: KernelMessage.IOptions = {
  373. msgType: 'kernel_info_request',
  374. channel: 'shell',
  375. username: this._username,
  376. session: this._clientId
  377. };
  378. let msg = KernelMessage.createShellMessage(options);
  379. let reply = (await Private.handleShellMessage(this, msg)) as KernelMessage.IInfoReplyMsg;
  380. if (this.isDisposed) {
  381. throw new Error('Disposed kernel');
  382. }
  383. this._info = reply.content;
  384. return reply;
  385. }
  386. /**
  387. * Send a `complete_request` message.
  388. *
  389. * #### Notes
  390. * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion).
  391. *
  392. * Fulfills with the `complete_reply` content when the shell reply is
  393. * received and validated.
  394. */
  395. requestComplete(content: KernelMessage.ICompleteRequest): Promise<KernelMessage.ICompleteReplyMsg> {
  396. let options: KernelMessage.IOptions = {
  397. msgType: 'complete_request',
  398. channel: 'shell',
  399. username: this._username,
  400. session: this._clientId
  401. };
  402. let msg = KernelMessage.createShellMessage(options, content);
  403. return Private.handleShellMessage(this, msg) as Promise<KernelMessage.ICompleteReplyMsg>;
  404. }
  405. /**
  406. * Send an `inspect_request` message.
  407. *
  408. * #### Notes
  409. * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#introspection).
  410. *
  411. * Fulfills with the `inspect_reply` content when the shell reply is
  412. * received and validated.
  413. */
  414. requestInspect(content: KernelMessage.IInspectRequest): Promise<KernelMessage.IInspectReplyMsg> {
  415. let options: KernelMessage.IOptions = {
  416. msgType: 'inspect_request',
  417. channel: 'shell',
  418. username: this._username,
  419. session: this._clientId
  420. };
  421. let msg = KernelMessage.createShellMessage(options, content);
  422. return Private.handleShellMessage(this, msg) as Promise<KernelMessage.IInspectReplyMsg>;
  423. }
  424. /**
  425. * Send a `history_request` message.
  426. *
  427. * #### Notes
  428. * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#history).
  429. *
  430. * Fulfills with the `history_reply` content when the shell reply is
  431. * received and validated.
  432. */
  433. requestHistory(content: KernelMessage.IHistoryRequest): Promise<KernelMessage.IHistoryReplyMsg> {
  434. let options: KernelMessage.IOptions = {
  435. msgType: 'history_request',
  436. channel: 'shell',
  437. username: this._username,
  438. session: this._clientId
  439. };
  440. let msg = KernelMessage.createShellMessage(options, content);
  441. return Private.handleShellMessage(this, msg) as Promise<KernelMessage.IHistoryReplyMsg>;
  442. }
  443. /**
  444. * Send an `execute_request` message.
  445. *
  446. * #### Notes
  447. * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute).
  448. *
  449. * Future `onReply` is called with the `execute_reply` content when the
  450. * shell reply is received and validated. The future will resolve when
  451. * this message is received and the `idle` iopub status is received.
  452. * The future will also be disposed at this point unless `disposeOnDone`
  453. * is specified and `false`, in which case it is up to the caller to dispose
  454. * of the future.
  455. *
  456. * **See also:** [[IExecuteReply]]
  457. */
  458. requestExecute(content: KernelMessage.IExecuteRequest, disposeOnDone: boolean = true): Kernel.IFuture {
  459. let options: KernelMessage.IOptions = {
  460. msgType: 'execute_request',
  461. channel: 'shell',
  462. username: this._username,
  463. session: this._clientId
  464. };
  465. let defaults: JSONObject = {
  466. silent : false,
  467. store_history : true,
  468. user_expressions : {},
  469. allow_stdin : true,
  470. stop_on_error : false
  471. };
  472. content = { ...defaults, ...content };
  473. let msg = KernelMessage.createShellMessage(options, content);
  474. return this.sendShellMessage(msg, true, disposeOnDone);
  475. }
  476. /**
  477. * Send an `is_complete_request` message.
  478. *
  479. * #### Notes
  480. * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#code-completeness).
  481. *
  482. * Fulfills with the `is_complete_response` content when the shell reply is
  483. * received and validated.
  484. */
  485. requestIsComplete(content: KernelMessage.IIsCompleteRequest): Promise<KernelMessage.IIsCompleteReplyMsg> {
  486. let options: KernelMessage.IOptions = {
  487. msgType: 'is_complete_request',
  488. channel: 'shell',
  489. username: this._username,
  490. session: this._clientId
  491. };
  492. let msg = KernelMessage.createShellMessage(options, content);
  493. return Private.handleShellMessage(this, msg) as Promise<KernelMessage.IIsCompleteReplyMsg>;
  494. }
  495. /**
  496. * Send a `comm_info_request` message.
  497. *
  498. * #### Notes
  499. * Fulfills with the `comm_info_reply` content when the shell reply is
  500. * received and validated.
  501. */
  502. requestCommInfo(content: KernelMessage.ICommInfoRequest): Promise<KernelMessage.ICommInfoReplyMsg> {
  503. let options: KernelMessage.IOptions = {
  504. msgType: 'comm_info_request',
  505. channel: 'shell',
  506. username: this._username,
  507. session: this._clientId
  508. };
  509. let msg = KernelMessage.createShellMessage(options, content);
  510. return Private.handleShellMessage(this, msg) as Promise<KernelMessage.ICommInfoReplyMsg>;
  511. }
  512. /**
  513. * Send an `input_reply` message.
  514. *
  515. * #### Notes
  516. * See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-stdin-router-dealer-sockets).
  517. */
  518. sendInputReply(content: KernelMessage.IInputReply): void {
  519. if (this.status === 'dead') {
  520. throw new Error('Kernel is dead');
  521. }
  522. let options: KernelMessage.IOptions = {
  523. msgType: 'input_reply',
  524. channel: 'stdin',
  525. username: this._username,
  526. session: this._clientId
  527. };
  528. let msg = KernelMessage.createMessage(options, content);
  529. if (!this._isReady || !this._ws) {
  530. this._pendingMessages.push(msg);
  531. } else {
  532. this._ws.send(serialize.serialize(msg));
  533. }
  534. this._anyMessage.emit({msg, direction: 'send'});
  535. }
  536. /**
  537. * Connect to a comm, or create a new one.
  538. *
  539. * #### Notes
  540. * If a client-side comm already exists with the given commId, it is returned.
  541. */
  542. connectToComm(targetName: string, commId: string = uuid()): Kernel.IComm {
  543. if (this._comms.has(commId)) {
  544. return this._comms.get(commId);
  545. }
  546. let comm = new CommHandler(
  547. targetName,
  548. commId,
  549. this,
  550. () => { this._unregisterComm(commId); }
  551. );
  552. this._comms.set(commId, comm);
  553. return comm;
  554. }
  555. /**
  556. * Register a comm target handler.
  557. *
  558. * @param targetName - The name of the comm target.
  559. *
  560. * @param callback - The callback invoked for a comm open message.
  561. *
  562. * @returns A disposable used to unregister the comm target.
  563. *
  564. * #### Notes
  565. * Only one comm target can be registered to a target name at a time, an
  566. * existing callback for the same target name will be overidden. A registered
  567. * comm target handler will take precedence over a comm which specifies a
  568. * `target_module`.
  569. *
  570. * If the callback returns a promise, kernel message processing will pause
  571. * until the returned promise is fulfilled.
  572. *
  573. * TODO: perhaps, just like with registerMessageHook, we should just
  574. * provide a removeCommTarget function instead of returning a disposable.
  575. * Presumably it's just as easy for someone to store the comm target name as
  576. * it is to store the disposable. Since there is only one callback, you don't even
  577. * need to store the callback.
  578. */
  579. registerCommTarget(targetName: string, callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike<void>): void {
  580. this._targetRegistry[targetName] = callback;
  581. }
  582. /**
  583. * Remove a comm target handler.
  584. *
  585. * @param targetName - The name of the comm target to remove.
  586. *
  587. * @param callback - The callback to remove.
  588. *
  589. * #### Notes
  590. * The comm target is only removed if it matches the callback argument.
  591. */
  592. removeCommTarget(targetName: string, callback: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void | PromiseLike<void>): void {
  593. if (!this.isDisposed && this._targetRegistry[targetName] === callback) {
  594. delete this._targetRegistry[targetName];
  595. }
  596. }
  597. /**
  598. * Register an IOPub message hook.
  599. *
  600. * @param msg_id - The parent_header message id the hook will intercept.
  601. *
  602. * @param hook - The callback invoked for the message.
  603. *
  604. * #### Notes
  605. * The IOPub hook system allows you to preempt the handlers for IOPub
  606. * messages that are responses to a given message id.
  607. *
  608. * The most recently registered hook is run first. A hook can return a
  609. * boolean or a promise to a boolean, in which case all kernel message
  610. * processing pauses until the promise is fulfilled. If a hook return value
  611. * resolves to false, any later hooks will not run and the function will
  612. * return a promise resolving to false. If a hook throws an error, the error
  613. * is logged to the console and the next hook is run. If a hook is
  614. * registered during the hook processing, it will not run until the next
  615. * message. If a hook is removed during the hook processing, it will be
  616. * deactivated immediately.
  617. *
  618. * See also [[IFuture.registerMessageHook]].
  619. */
  620. registerMessageHook(msgId: string, hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>): void {
  621. let future = this._futures && this._futures.get(msgId);
  622. if (future) {
  623. future.registerMessageHook(hook);
  624. }
  625. }
  626. /**
  627. * Remove an IOPub message hook.
  628. *
  629. * @param msg_id - The parent_header message id the hook intercepted.
  630. *
  631. * @param hook - The callback invoked for the message.
  632. *
  633. */
  634. removeMessageHook(msgId: string, hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>): void {
  635. let future = this._futures && this._futures.get(msgId);
  636. if (future) {
  637. future.removeMessageHook(hook);
  638. }
  639. }
  640. /**
  641. * Handle a message with a display id.
  642. *
  643. * @returns Whether the message was handled.
  644. */
  645. private async _handleDisplayId(displayId: string, msg: KernelMessage.IMessage): Promise<boolean> {
  646. let msgId = (msg.parent_header as KernelMessage.IHeader).msg_id;
  647. let parentIds = this._displayIdToParentIds.get(displayId);
  648. if (parentIds) {
  649. // We've seen it before, update existing outputs with same display_id
  650. // by handling display_data as update_display_data.
  651. let updateMsg: KernelMessage.IMessage = {
  652. header: JSONExt.deepCopy(msg.header) as KernelMessage.IHeader,
  653. parent_header: JSONExt.deepCopy(msg.parent_header) as KernelMessage.IHeader,
  654. metadata: JSONExt.deepCopy(msg.metadata),
  655. content: JSONExt.deepCopy(msg.content),
  656. channel: msg.channel,
  657. buffers: msg.buffers ? msg.buffers.slice() : []
  658. };
  659. (updateMsg.header as any).msg_type = 'update_display_data';
  660. await Promise.all(parentIds.map(async (parentId) => {
  661. let future = this._futures && this._futures.get(parentId);
  662. if (future) {
  663. await future.handleMsg(updateMsg);
  664. }
  665. }));
  666. }
  667. // We're done here if it's update_display.
  668. if (msg.header.msg_type === 'update_display_data') {
  669. // It's an update, don't proceed to the normal display.
  670. return true;
  671. }
  672. // Regular display_data with id, record it for future updating
  673. // in _displayIdToParentIds for future lookup.
  674. parentIds = this._displayIdToParentIds.get(displayId) || [];
  675. if (parentIds.indexOf(msgId) === -1) {
  676. parentIds.push(msgId);
  677. }
  678. this._displayIdToParentIds.set(displayId, parentIds);
  679. // Add to our map of display ids for this message.
  680. let displayIds = this._msgIdToDisplayIds.get(msgId) || [];
  681. if (displayIds.indexOf(msgId) === -1) {
  682. displayIds.push(msgId);
  683. }
  684. this._msgIdToDisplayIds.set(msgId, displayIds);
  685. // Let the message propagate to the intended recipient.
  686. return false;
  687. }
  688. /**
  689. * Clear the socket state.
  690. */
  691. private _clearSocket(): void {
  692. this._wsStopped = true;
  693. this._isReady = false;
  694. if (this._ws !== null) {
  695. // Clear the websocket event handlers and the socket itself.
  696. this._ws.onopen = this._noOp;
  697. this._ws.onclose = this._noOp;
  698. this._ws.onerror = this._noOp;
  699. this._ws.onmessage = this._noOp;
  700. log('closing ws connection: ' + this.id.slice(0, 6));
  701. this._ws.close();
  702. this._ws = null;
  703. }
  704. }
  705. /**
  706. * Handle status iopub messages from the kernel.
  707. */
  708. private _updateStatus(status: Kernel.Status): void {
  709. switch (status) {
  710. case 'starting':
  711. case 'idle':
  712. case 'busy':
  713. case 'connected':
  714. this._isReady = true;
  715. break;
  716. case 'restarting':
  717. case 'reconnecting':
  718. case 'dead':
  719. this._isReady = false;
  720. break;
  721. default:
  722. console.error('invalid kernel status:', status);
  723. return;
  724. }
  725. if (status !== this._status) {
  726. this._status = status;
  727. Private.logKernelStatus(this);
  728. this._statusChanged.emit(status);
  729. if (status === 'dead') {
  730. this.dispose();
  731. }
  732. }
  733. if (this._isReady) {
  734. this._sendPending();
  735. }
  736. }
  737. /**
  738. * Send pending messages to the kernel.
  739. */
  740. private _sendPending(): void {
  741. // We shift the message off the queue
  742. // after the message is sent so that if there is an exception,
  743. // the message is still pending.
  744. while (this._ws && this._pendingMessages.length > 0) {
  745. let msg = serialize.serialize(this._pendingMessages[0]);
  746. this._ws.send(msg);
  747. this._pendingMessages.shift();
  748. }
  749. }
  750. /**
  751. * Clear the internal state.
  752. */
  753. private _clearState(): void {
  754. this._isReady = false;
  755. this._pendingMessages = [];
  756. this._futures.forEach(future => { future.dispose(); });
  757. this._comms.forEach(comm => { comm.dispose(); });
  758. this._msgChain = Promise.resolve();
  759. this._kernelSession = '';
  760. this._futures = new Map<string, KernelFutureHandler>();
  761. this._comms = new Map<string, Kernel.IComm>();
  762. this._displayIdToParentIds.clear();
  763. this._msgIdToDisplayIds.clear();
  764. }
  765. /**
  766. * Check to make sure it is okay to proceed to handle a message.
  767. *
  768. * #### Notes
  769. * Because we handle messages asynchronously, before a message is handled the
  770. * kernel might be disposed or restarted (and have a different session id).
  771. * This function throws an error in each of these cases. This is meant to be
  772. * called at the start of an asynchronous message handler to cancel message
  773. * processing if the message no longer is valid.
  774. */
  775. private _assertCurrentMessage(msg: KernelMessage.IMessage) {
  776. if (this.isDisposed) {
  777. log(msg);
  778. throw new Error('Kernel object is disposed');
  779. }
  780. if (msg.header.session !== this._kernelSession) {
  781. throw new Error(`Canceling handling of old message: ${msg.header.msg_type}`);
  782. }
  783. }
  784. /**
  785. * Handle a `comm_open` kernel message.
  786. */
  787. private async _handleCommOpen(msg: KernelMessage.ICommOpenMsg): Promise<void> {
  788. this._assertCurrentMessage(msg);
  789. let content = msg.content;
  790. let comm = new CommHandler(
  791. content.target_name,
  792. content.comm_id,
  793. this,
  794. () => { this._unregisterComm(content.comm_id); }
  795. );
  796. this._comms.set(content.comm_id, comm);
  797. try {
  798. let target = await Private.loadObject(content.target_name, content.target_module, this._targetRegistry);
  799. await target(comm, msg);
  800. } catch (e) {
  801. // Close the comm asynchronously. We cannot block message processing on
  802. // kernel messages to wait for another kernel message.
  803. comm.close();
  804. console.error('Exception opening new comm');
  805. throw(e);
  806. }
  807. }
  808. /**
  809. * Handle 'comm_close' kernel message.
  810. */
  811. private async _handleCommClose(msg: KernelMessage.ICommCloseMsg): Promise<void> {
  812. this._assertCurrentMessage(msg);
  813. let content = msg.content;
  814. let comm = this._comms.get(content.comm_id);
  815. if (!comm) {
  816. console.error('Comm not found for comm id ' + content.comm_id);
  817. return;
  818. }
  819. this._unregisterComm(comm.commId);
  820. let onClose = comm.onClose;
  821. if (onClose) {
  822. await onClose(msg);
  823. }
  824. (comm as CommHandler).dispose();
  825. }
  826. /**
  827. * Handle a 'comm_msg' kernel message.
  828. */
  829. private async _handleCommMsg(msg: KernelMessage.ICommMsgMsg): Promise<void> {
  830. this._assertCurrentMessage(msg);
  831. let content = msg.content;
  832. let comm = this._comms.get(content.comm_id);
  833. if (!comm) {
  834. return;
  835. }
  836. let onMsg = comm.onMsg;
  837. if (onMsg) {
  838. await onMsg(msg);
  839. }
  840. }
  841. /**
  842. * Unregister a comm instance.
  843. */
  844. private _unregisterComm(commId: string) {
  845. this._comms.delete(commId);
  846. }
  847. /**
  848. * Create the kernel websocket connection and add socket status handlers.
  849. */
  850. private _createSocket = () => {
  851. let settings = this.serverSettings;
  852. let partialUrl = URLExt.join(settings.wsUrl, KERNEL_SERVICE_URL,
  853. encodeURIComponent(this._id));
  854. // Strip any authentication from the display string.
  855. // TODO - Audit tests for extra websockets started
  856. let display = partialUrl.replace(/^((?:\w+:)?\/\/)(?:[^@\/]+@)/, '$1');
  857. console.log('Starting WebSocket:', display);
  858. let url = URLExt.join(
  859. partialUrl,
  860. 'channels?session_id=' + encodeURIComponent(this._clientId)
  861. );
  862. // If token authentication is in use.
  863. let token = settings.token;
  864. if (token !== '') {
  865. url = url + `&token=${encodeURIComponent(token)}`;
  866. }
  867. this._connectionPromise = new PromiseDelegate<void>();
  868. this._wsStopped = false;
  869. this._ws = new settings.WebSocket(url);
  870. // Ensure incoming binary messages are not Blobs
  871. this._ws.binaryType = 'arraybuffer';
  872. this._ws.onmessage = this._onWSMessage;
  873. this._ws.onopen = this._onWSOpen;
  874. this._ws.onclose = this._onWSClose;
  875. this._ws.onerror = this._onWSClose;
  876. }
  877. /**
  878. * Handle a websocket open event.
  879. */
  880. private _onWSOpen = (evt: Event) => {
  881. this._reconnectAttempt = 0;
  882. // Allow the message to get through.
  883. this._isReady = true;
  884. // Update our status to connected.
  885. this._updateStatus('connected');
  886. // Get the kernel info, signaling that the kernel is ready.
  887. // TODO: requestKernelInfo shouldn't make a request, but should return cached info?
  888. this.requestKernelInfo().then(() => {
  889. this._connectionPromise.resolve(void 0);
  890. }).catch(err => {
  891. this._connectionPromise.reject(err);
  892. });
  893. this._isReady = false;
  894. }
  895. /**
  896. * Handle a websocket message, validating and routing appropriately.
  897. */
  898. private _onWSMessage = (evt: MessageEvent) => {
  899. if (this._wsStopped) {
  900. // If the socket is being closed, ignore any messages
  901. return;
  902. }
  903. // Notify immediately if there is an error with the message.
  904. let msg: KernelMessage.IMessage;
  905. try {
  906. msg = serialize.deserialize(evt.data);
  907. validate.validateMessage(msg);
  908. } catch (error) {
  909. error.message = `Kernel message validation error: ${error.message}`;
  910. // We throw the error so that it bubbles up to the top, and displays the right stack.
  911. throw error;
  912. }
  913. let parentId = '';
  914. if ((msg.parent_header as any).msg_id) {
  915. parentId = 'P' + (msg.parent_header as any).msg_id.slice(0, 6) + ' ';
  916. }
  917. let msgType = msg.header.msg_type;
  918. if (msgType === 'status') {
  919. msgType += ' ' + (msg.content as any).execution_state;
  920. }
  921. log(`JS KERNEL RECEIVED MESSAGE: K${this.id.slice(0, 6)} C${this.clientId.slice(0, 6)} ${parentId}M${msg.header.msg_id.slice(0, 6)} ${msgType} `);
  922. // Update the current kernel session id
  923. this._kernelSession = msg.header.session;
  924. // Handle the message asynchronously, in the order received.
  925. this._msgChain = this._msgChain.then(() => {
  926. // If the message isn't a kernel_info_reply, check to make sure it
  927. // corresponds to the current kernel. kernel_info_reply messages can
  928. // change the kernel session, so we allow those to pass.
  929. if (msg.header.msg_type !== 'kernel_info_reply') {
  930. this._assertCurrentMessage(msg);
  931. }
  932. // Return so that any promises from handling a message are fulfilled
  933. // before proceeding to the next message.
  934. return this._handleMessage(msg);
  935. }).catch(error => {
  936. // Log any errors in handling the message, thus resetting the _msgChain
  937. // promise so we can process more messages.
  938. console.error(error);
  939. });
  940. // Emit the message recieve signal
  941. this._anyMessage.emit({msg, direction: 'recv'});
  942. }
  943. private async _handleMessage(msg: KernelMessage.IMessage): Promise<void> {
  944. let handled = false;
  945. // Check to see if we have a display_id we need to reroute.
  946. if (msg.parent_header && msg.channel === 'iopub') {
  947. switch (msg.header.msg_type) {
  948. case 'display_data':
  949. case 'update_display_data':
  950. case 'execute_result':
  951. // display_data messages may re-route based on their display_id.
  952. let transient = (msg.content.transient || {}) as JSONObject;
  953. let displayId = transient['display_id'] as string;
  954. if (displayId) {
  955. handled = await this._handleDisplayId(displayId, msg);
  956. // The await above may make this message out of date, so check again.
  957. this._assertCurrentMessage(msg);
  958. }
  959. break;
  960. default:
  961. break;
  962. }
  963. }
  964. if (!handled && msg.parent_header) {
  965. let parentHeader = msg.parent_header as KernelMessage.IHeader;
  966. let future = this._futures && this._futures.get(parentHeader.msg_id);
  967. if (future) {
  968. await future.handleMsg(msg);
  969. this._assertCurrentMessage(msg);
  970. } else {
  971. // If the message was sent by us and was not iopub, it is orphaned.
  972. let owned = parentHeader.session === this.clientId;
  973. if (msg.channel !== 'iopub' && owned) {
  974. this._unhandledMessage.emit(msg);
  975. }
  976. }
  977. }
  978. if (msg.channel === 'iopub') {
  979. switch (msg.header.msg_type) {
  980. case 'status':
  981. // Updating the status is synchronous, and we call no async user code
  982. this._updateStatus((msg as KernelMessage.IStatusMsg).content.execution_state);
  983. break;
  984. case 'comm_open':
  985. await this._handleCommOpen(msg as KernelMessage.ICommOpenMsg);
  986. break;
  987. case 'comm_msg':
  988. await this._handleCommMsg(msg as KernelMessage.ICommMsgMsg);
  989. break;
  990. case 'comm_close':
  991. await this._handleCommClose(msg as KernelMessage.ICommCloseMsg);
  992. break;
  993. default:
  994. break;
  995. }
  996. // If the message was a status dead message, we might have disposed ourselves.
  997. if (!this.isDisposed) {
  998. this._assertCurrentMessage(msg);
  999. // the message wouldn't be emitted if we were disposed anyway.
  1000. this._iopubMessage.emit(msg as KernelMessage.IIOPubMessage);
  1001. }
  1002. }
  1003. }
  1004. /**
  1005. * Handle a websocket close event.
  1006. */
  1007. private _onWSClose = (evt: Event) => {
  1008. if (this._wsStopped || !this._ws) {
  1009. return;
  1010. }
  1011. log('WS Closed');
  1012. // Clear the websocket event handlers and the socket itself.
  1013. this._ws.onclose = this._noOp;
  1014. this._ws.onerror = this._noOp;
  1015. this._ws = null;
  1016. if (this._reconnectAttempt < this._reconnectLimit) {
  1017. this._updateStatus('reconnecting');
  1018. let timeout = Math.pow(2, this._reconnectAttempt);
  1019. console.error('Connection lost, reconnecting in ' + timeout + ' seconds.');
  1020. setTimeout(this._createSocket, 1e3 * timeout);
  1021. this._reconnectAttempt += 1;
  1022. } else {
  1023. this._updateStatus('dead');
  1024. this._connectionPromise.reject(new Error('Could not establish connection'));
  1025. }
  1026. }
  1027. private _id = '';
  1028. private _name = '';
  1029. private _status: Kernel.Status = 'unknown';
  1030. private _kernelSession = '';
  1031. private _clientId = '';
  1032. private _isDisposed = false;
  1033. private _wsStopped = false;
  1034. private _ws: WebSocket | null = null;
  1035. private _username = '';
  1036. private _reconnectLimit = 7;
  1037. private _reconnectAttempt = 0;
  1038. private _isReady = false;
  1039. private _futures: Map<string, KernelFutureHandler>;
  1040. private _comms: Map<string, Kernel.IComm>;
  1041. private _targetRegistry: { [key: string]: (comm: Kernel.IComm, msg: KernelMessage.ICommOpenMsg) => void; } = Object.create(null);
  1042. private _info: KernelMessage.IInfoReply | null = null;
  1043. private _pendingMessages: KernelMessage.IMessage[] = [];
  1044. private _connectionPromise: PromiseDelegate<void>;
  1045. private _specPromise: Promise<Kernel.ISpecModel>;
  1046. private _statusChanged = new Signal<this, Kernel.Status>(this);
  1047. private _iopubMessage = new Signal<this, KernelMessage.IIOPubMessage>(this);
  1048. private _anyMessage = new Signal<this, Kernel.IAnyMessageArgs>(this);
  1049. private _unhandledMessage = new Signal<this, KernelMessage.IMessage>(this);
  1050. private _displayIdToParentIds = new Map<string, string[]>();
  1051. private _msgIdToDisplayIds = new Map<string, string[]>();
  1052. private _terminated = new Signal<this, void>(this);
  1053. private _msgChain = Promise.resolve();
  1054. private _noOp = () => { /* no-op */};
  1055. }
  1056. /**
  1057. * The namespace for `DefaultKernel` statics.
  1058. */
  1059. export
  1060. namespace DefaultKernel {
  1061. /**
  1062. * Find a kernel by id.
  1063. *
  1064. * @param id - The id of the kernel of interest.
  1065. *
  1066. * @param settings - The optional server settings.
  1067. *
  1068. * @returns A promise that resolves with the model for the kernel.
  1069. *
  1070. * #### Notes
  1071. * If the kernel was already started via `startNewKernel`, we return its
  1072. * `Kernel.IModel`.
  1073. *
  1074. * Otherwise, we attempt to find to the existing
  1075. * kernel.
  1076. * The promise is fulfilled when the kernel is found,
  1077. * otherwise the promise is rejected.
  1078. */
  1079. export
  1080. function findById(id: string, settings?: ServerConnection.ISettings): Promise<Kernel.IModel> {
  1081. return Private.findById(id, settings);
  1082. }
  1083. /**
  1084. * Fetch all of the kernel specs.
  1085. *
  1086. * @param settings - The optional server settings.
  1087. *
  1088. * @returns A promise that resolves with the kernel specs.
  1089. *
  1090. * #### Notes
  1091. * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernelspecs).
  1092. */
  1093. export
  1094. function getSpecs(settings?: ServerConnection.ISettings): Promise<Kernel.ISpecModels> {
  1095. return Private.getSpecs(settings);
  1096. }
  1097. /**
  1098. * Fetch the running kernels.
  1099. *
  1100. * @param settings - The optional server settings.
  1101. *
  1102. * @returns A promise that resolves with the list of running kernels.
  1103. *
  1104. * #### Notes
  1105. * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels) and validates the response model.
  1106. *
  1107. * The promise is fulfilled on a valid response and rejected otherwise.
  1108. */
  1109. export
  1110. function listRunning(settings?: ServerConnection.ISettings): Promise<Kernel.IModel[]> {
  1111. return Private.listRunning(settings);
  1112. }
  1113. /**
  1114. * Start a new kernel.
  1115. *
  1116. * @param options - The options used to create the kernel.
  1117. *
  1118. * @returns A promise that resolves with a kernel object.
  1119. *
  1120. * #### Notes
  1121. * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels) and validates the response model.
  1122. *
  1123. * If no options are given or the kernel name is not given, the
  1124. * default kernel will by started by the server.
  1125. *
  1126. * Wraps the result in a Kernel object. The promise is fulfilled
  1127. * when the kernel is started by the server, otherwise the promise is rejected.
  1128. */
  1129. export
  1130. function startNew(options: Kernel.IOptions): Promise<Kernel.IKernel> {
  1131. return Private.startNew(options);
  1132. }
  1133. /**
  1134. * Connect to a running kernel.
  1135. *
  1136. * @param model - The model of the running kernel.
  1137. *
  1138. * @param settings - The server settings for the request.
  1139. *
  1140. * @returns A promise that resolves with the kernel object.
  1141. *
  1142. * #### Notes
  1143. * If the kernel was already started via `startNewKernel`, the existing
  1144. * Kernel object info is used to create another instance.
  1145. */
  1146. export
  1147. function connectTo(model: Kernel.IModel, settings?: ServerConnection.ISettings): Promise<Kernel.IKernel> {
  1148. return Private.connectTo(model, settings);
  1149. }
  1150. /**
  1151. * Shut down a kernel by id.
  1152. *
  1153. * @param id - The id of the running kernel.
  1154. *
  1155. * @param settings - The server settings for the request.
  1156. *
  1157. * @returns A promise that resolves when the kernel is shut down.
  1158. */
  1159. export
  1160. function shutdown(id: string, settings?: ServerConnection.ISettings): Promise<void> {
  1161. return Private.shutdownKernel(id, settings);
  1162. }
  1163. /**
  1164. * Shut down all kernels.
  1165. *
  1166. * @param settings - The server settings to use.
  1167. *
  1168. * @returns A promise that resolves when all the kernels are shut down.
  1169. */
  1170. export
  1171. function shutdownAll(settings?: ServerConnection.ISettings): Promise<void> {
  1172. return Private.shutdownAll(settings);
  1173. }
  1174. }
  1175. /**
  1176. * A private namespace for the Kernel.
  1177. */
  1178. namespace Private {
  1179. /**
  1180. * A module private store for running kernels.
  1181. */
  1182. export
  1183. const runningKernels: DefaultKernel[] = [];
  1184. /**
  1185. * A module private store of kernel specs by base url.
  1186. */
  1187. export
  1188. const specs: { [key: string]: Promise<Kernel.ISpecModels> } = Object.create(null);
  1189. /**
  1190. * Find a kernel by id.
  1191. */
  1192. export
  1193. function findById(id: string, settings?: ServerConnection.ISettings): Promise<Kernel.IModel> {
  1194. let kernel = find(runningKernels, value => {
  1195. return (value.id === id);
  1196. });
  1197. if (kernel) {
  1198. return Promise.resolve(kernel.model);
  1199. }
  1200. return getKernelModel(id, settings).catch(() => {
  1201. throw new Error(`No running kernel with id: ${id}`);
  1202. });
  1203. }
  1204. /**
  1205. * Get the cached kernel specs or fetch them.
  1206. */
  1207. export
  1208. function findSpecs(settings?: ServerConnection.ISettings): Promise<Kernel.ISpecModels> {
  1209. settings = settings || ServerConnection.makeSettings();
  1210. let promise = specs[settings.baseUrl];
  1211. if (promise) {
  1212. return promise;
  1213. }
  1214. return getSpecs(settings);
  1215. }
  1216. /**
  1217. * Fetch all of the kernel specs.
  1218. *
  1219. * #### Notes
  1220. * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernelspecs).
  1221. */
  1222. export
  1223. function getSpecs(settings?: ServerConnection.ISettings): Promise<Kernel.ISpecModels> {
  1224. settings = settings || ServerConnection.makeSettings();
  1225. let url = URLExt.join(settings.baseUrl, KERNELSPEC_SERVICE_URL);
  1226. let promise = ServerConnection.makeRequest(url, {}, settings).then(response => {
  1227. if (response.status !== 200) {
  1228. throw new ServerConnection.ResponseError(response);
  1229. }
  1230. return response.json();
  1231. }).then(data => {
  1232. return validate.validateSpecModels(data);
  1233. });
  1234. Private.specs[settings.baseUrl] = promise;
  1235. return promise;
  1236. }
  1237. /**
  1238. * Fetch the running kernels.
  1239. *
  1240. * #### Notes
  1241. * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels) and validates the response model.
  1242. *
  1243. * The promise is fulfilled on a valid response and rejected otherwise.
  1244. */
  1245. export
  1246. function listRunning(settings?: ServerConnection.ISettings): Promise<Kernel.IModel[]> {
  1247. settings = settings || ServerConnection.makeSettings();
  1248. let url = URLExt.join(settings.baseUrl, KERNEL_SERVICE_URL);
  1249. return ServerConnection.makeRequest(url, {}, settings).then(response => {
  1250. if (response.status !== 200) {
  1251. throw new ServerConnection.ResponseError(response);
  1252. }
  1253. return response.json();
  1254. }).then(data => {
  1255. if (!Array.isArray(data)) {
  1256. throw new Error('Invalid kernel list');
  1257. }
  1258. for (let i = 0; i < data.length; i++) {
  1259. validate.validateModel(data[i]);
  1260. }
  1261. return updateRunningKernels(data);
  1262. });
  1263. }
  1264. /**
  1265. * Update the running kernels based on new data from the server.
  1266. */
  1267. export
  1268. function updateRunningKernels(kernels: Kernel.IModel[]): Kernel.IModel[] {
  1269. each(runningKernels, kernel => {
  1270. let updated = find(kernels, model => {
  1271. return kernel.id === model.id;
  1272. });
  1273. // If kernel is no longer running on disk, emit dead signal.
  1274. if (!updated && kernel.status !== 'dead') {
  1275. kernel.dispose();
  1276. }
  1277. });
  1278. return kernels;
  1279. }
  1280. /**
  1281. * Start a new kernel.
  1282. */
  1283. export
  1284. async function startNew(options: Kernel.IOptions): Promise<Kernel.IKernel> {
  1285. let settings = options.serverSettings || ServerConnection.makeSettings();
  1286. let url = URLExt.join(settings.baseUrl, KERNEL_SERVICE_URL);
  1287. let init = {
  1288. method: 'POST',
  1289. body: JSON.stringify({ name: options.name })
  1290. };
  1291. let response = await ServerConnection.makeRequest(url, init, settings);
  1292. if (response.status !== 201) {
  1293. throw new ServerConnection.ResponseError(response);
  1294. }
  1295. let data = await response.json();
  1296. validate.validateModel(data);
  1297. return new DefaultKernel({
  1298. ...options,
  1299. name: data.name,
  1300. serverSettings: settings
  1301. }, data.id);
  1302. }
  1303. /**
  1304. * Connect to a running kernel.
  1305. *
  1306. * TODO: why is this function async?
  1307. */
  1308. export
  1309. async function connectTo(model: Kernel.IModel, settings?: ServerConnection.ISettings): Promise<Kernel.IKernel> {
  1310. let serverSettings = settings || ServerConnection.makeSettings();
  1311. let kernel = find(runningKernels, value => {
  1312. return value.id === model.id;
  1313. });
  1314. if (kernel) {
  1315. return kernel.clone();
  1316. }
  1317. return new DefaultKernel(
  1318. { name: model.name, serverSettings }, model.id
  1319. );
  1320. }
  1321. /**
  1322. * Restart a kernel.
  1323. */
  1324. export
  1325. async function restartKernel(kernel: Kernel.IKernel, settings?: ServerConnection.ISettings): Promise<void> {
  1326. if (kernel.status === 'dead') {
  1327. throw new Error('Kernel is dead');
  1328. }
  1329. settings = settings || ServerConnection.makeSettings();
  1330. let url = URLExt.join(
  1331. settings.baseUrl, KERNEL_SERVICE_URL,
  1332. encodeURIComponent(kernel.id), 'restart'
  1333. );
  1334. let init = { method: 'POST' };
  1335. // TODO: If we handleRestart before making the server request, we sever the
  1336. // communication link before the shutdown_reply message comes, so we end up
  1337. // getting the shutdown_reply messages after we reconnect, which is weird.
  1338. // We might want to move the handleRestart to after we get the response back
  1339. // Handle the restart on all of the kernels with the same id.
  1340. each(runningKernels, k => {
  1341. if (k.id === kernel.id) {
  1342. k.handleRestart();
  1343. }
  1344. });
  1345. let response = await ServerConnection.makeRequest(url, init, settings);
  1346. if (response.status !== 200) {
  1347. throw new ServerConnection.ResponseError(response);
  1348. }
  1349. let data = await response.json();
  1350. validate.validateModel(data);
  1351. // Reconnect the other kernels asynchronously, but don't wait for them.
  1352. each(runningKernels, k => {
  1353. if (k !== kernel && k.id === kernel.id) {
  1354. k.reconnect();
  1355. }
  1356. });
  1357. await kernel.reconnect();
  1358. }
  1359. /**
  1360. * Interrupt a kernel.
  1361. */
  1362. export
  1363. async function interruptKernel(kernel: Kernel.IKernel, settings?: ServerConnection.ISettings): Promise<void> {
  1364. if (kernel.status === 'dead') {
  1365. throw new Error('Kernel is dead');
  1366. }
  1367. settings = settings || ServerConnection.makeSettings();
  1368. let url = URLExt.join(
  1369. settings.baseUrl, KERNEL_SERVICE_URL,
  1370. encodeURIComponent(kernel.id), 'interrupt'
  1371. );
  1372. let init = { method: 'POST' };
  1373. let response = await ServerConnection.makeRequest(url, init, settings);
  1374. if (response.status !== 204) {
  1375. throw new ServerConnection.ResponseError(response);
  1376. }
  1377. }
  1378. /**
  1379. * Delete a kernel.
  1380. */
  1381. export
  1382. async function shutdownKernel(id: string, settings?: ServerConnection.ISettings): Promise<void> {
  1383. settings = settings || ServerConnection.makeSettings();
  1384. let url = URLExt.join(settings.baseUrl, KERNEL_SERVICE_URL,
  1385. encodeURIComponent(id));
  1386. let init = { method: 'DELETE' };
  1387. let response = await ServerConnection.makeRequest(url, init, settings);
  1388. if (response.status === 404) {
  1389. let msg = `The kernel "${id}" does not exist on the server`;
  1390. console.warn(msg);
  1391. } else if (response.status !== 204) {
  1392. throw new ServerConnection.ResponseError(response);
  1393. }
  1394. killKernels(id);
  1395. }
  1396. /**
  1397. * Shut down all kernels.
  1398. *
  1399. * @param settings - The server settings to use.
  1400. *
  1401. * @returns A promise that resolves when all the kernels are shut down.
  1402. */
  1403. export
  1404. async function shutdownAll(settings?: ServerConnection.ISettings): Promise<void> {
  1405. settings = settings || ServerConnection.makeSettings();
  1406. let running = await listRunning(settings);
  1407. await Promise.all(running.map(k => shutdownKernel(k.id, settings)));
  1408. }
  1409. /**
  1410. * Kill the kernels by id.
  1411. */
  1412. function killKernels(id: string): void {
  1413. // Iterate on an array copy so disposals will not affect the iteration.
  1414. runningKernels.slice().forEach(kernel => {
  1415. if (kernel.id === id) {
  1416. kernel.dispose();
  1417. }
  1418. });
  1419. }
  1420. /**
  1421. * Get a full kernel model from the server by kernel id string.
  1422. */
  1423. export
  1424. async function getKernelModel(id: string, settings?: ServerConnection.ISettings): Promise<Kernel.IModel> {
  1425. settings = settings || ServerConnection.makeSettings();
  1426. let url = URLExt.join(settings.baseUrl, KERNEL_SERVICE_URL,
  1427. encodeURIComponent(id));
  1428. let response = await ServerConnection.makeRequest(url, {}, settings);
  1429. if (response.status !== 200) {
  1430. throw new ServerConnection.ResponseError(response);
  1431. }
  1432. let data = await response.json();
  1433. validate.validateModel(data);
  1434. return data;
  1435. }
  1436. /**
  1437. * Log the current kernel status.
  1438. */
  1439. export
  1440. function logKernelStatus(kernel: Kernel.IKernel): void {
  1441. switch (kernel.status) {
  1442. case 'idle':
  1443. case 'busy':
  1444. case 'unknown':
  1445. return;
  1446. default:
  1447. console.log(`Kernel: ${kernel.status} (${kernel.id})`);
  1448. break;
  1449. }
  1450. }
  1451. /**
  1452. * Send a kernel message to the kernel and resolve the reply message.
  1453. */
  1454. export
  1455. async function handleShellMessage(kernel: Kernel.IKernel, msg: KernelMessage.IShellMessage): Promise<KernelMessage.IShellMessage> {
  1456. let future = kernel.sendShellMessage(msg, true);
  1457. return future.done;
  1458. }
  1459. /**
  1460. * Try to load an object from a module or a registry.
  1461. *
  1462. * Try to load an object from a module asynchronously if a module
  1463. * is specified, otherwise tries to load an object from the global
  1464. * registry, if the global registry is provided.
  1465. */
  1466. export
  1467. function loadObject(name: string, moduleName: string | undefined, registry?: { [key: string]: any }): Promise<any> {
  1468. return new Promise((resolve, reject) => {
  1469. // Try loading the view module using require.js
  1470. if (moduleName) {
  1471. if (typeof requirejs === 'undefined') {
  1472. throw new Error('requirejs not found');
  1473. }
  1474. requirejs([moduleName], (mod: any) => {
  1475. if (mod[name] === void 0) {
  1476. let msg = `Object '${name}' not found in module '${moduleName}'`;
  1477. reject(new Error(msg));
  1478. } else {
  1479. resolve(mod[name]);
  1480. }
  1481. }, reject);
  1482. } else {
  1483. if (registry && registry[name]) {
  1484. resolve(registry[name]);
  1485. } else {
  1486. reject(new Error(`Object '${name}' not found in registry`));
  1487. }
  1488. }
  1489. });
  1490. }
  1491. }