default.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  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. ISignal, Signal
  11. } from '@phosphor/signaling';
  12. import {
  13. Kernel, KernelMessage
  14. } from '../kernel';
  15. import {
  16. ServerConnection
  17. } from '..';
  18. import {
  19. Session
  20. } from './session';
  21. import * as validate
  22. from './validate';
  23. /**
  24. * The url for the session service.
  25. */
  26. const SESSION_SERVICE_URL = 'api/sessions';
  27. /**
  28. * Session object for accessing the session REST api. The session
  29. * should be used to start kernels and then shut them down -- for
  30. * all other operations, the kernel object should be used.
  31. */
  32. export
  33. class DefaultSession implements Session.ISession {
  34. /**
  35. * Construct a new session.
  36. */
  37. constructor(options: Session.IOptions, id: string, kernel: Kernel.IKernel) {
  38. this._id = id;
  39. this._path = options.path;
  40. this._type = options.type;
  41. this._name = options.name;
  42. this.serverSettings = options.serverSettings || ServerConnection.makeSettings();
  43. this._uuid = uuid();
  44. Private.addRunning(this);
  45. this.setupKernel(kernel);
  46. this.terminated = new Signal<this, void>(this);
  47. }
  48. /**
  49. * A signal emitted when the session is shut down.
  50. */
  51. readonly terminated: Signal<this, void>;
  52. /**
  53. * A signal emitted when the kernel changes.
  54. */
  55. get kernelChanged(): ISignal<this, Kernel.IKernelConnection> {
  56. return this._kernelChanged;
  57. }
  58. /**
  59. * A signal emitted when the kernel status changes.
  60. */
  61. get statusChanged(): ISignal<this, Kernel.Status> {
  62. return this._statusChanged;
  63. }
  64. /**
  65. * A signal emitted for a kernel messages.
  66. */
  67. get iopubMessage(): ISignal<this, KernelMessage.IIOPubMessage> {
  68. return this._iopubMessage;
  69. }
  70. /**
  71. * A signal emitted for an unhandled kernel message.
  72. */
  73. get unhandledMessage(): ISignal<this, KernelMessage.IMessage> {
  74. return this._unhandledMessage;
  75. }
  76. /**
  77. * A signal emitted when a session property changes.
  78. */
  79. get propertyChanged(): ISignal<this, 'path' | 'name' | 'type'> {
  80. return this._propertyChanged;
  81. }
  82. /**
  83. * Get the session id.
  84. */
  85. get id(): string {
  86. return this._id;
  87. }
  88. /**
  89. * Get the session kernel object.
  90. *
  91. * #### Notes
  92. * This is a read-only property, and can be altered by [changeKernel].
  93. * Use the [statusChanged] and [unhandledMessage] signals on the session
  94. * instead of the ones on the kernel.
  95. */
  96. get kernel() : Kernel.IKernelConnection {
  97. return this._kernel;
  98. }
  99. /**
  100. * Get the session path.
  101. */
  102. get path(): string {
  103. return this._path;
  104. }
  105. /**
  106. * Get the session type.
  107. */
  108. get type(): string {
  109. return this._type;
  110. }
  111. /**
  112. * Get the session name.
  113. */
  114. get name(): string {
  115. return this._name;
  116. }
  117. /**
  118. * Get the model associated with the session.
  119. */
  120. get model(): Session.IModel {
  121. return {
  122. id: this.id,
  123. kernel: this.kernel.model,
  124. path: this._path,
  125. type: this._type,
  126. name: this._name
  127. };
  128. }
  129. /**
  130. * The current status of the session.
  131. *
  132. * #### Notes
  133. * This is a delegate to the kernel status.
  134. */
  135. get status(): Kernel.Status {
  136. return this._kernel ? this._kernel.status : 'dead';
  137. }
  138. /**
  139. * The server settings of the session.
  140. */
  141. readonly serverSettings: ServerConnection.ISettings;
  142. /**
  143. * Test whether the session has been disposed.
  144. */
  145. get isDisposed(): boolean {
  146. return this._isDisposed === true;
  147. }
  148. /**
  149. * Clone the current session with a new clientId.
  150. */
  151. clone(): Promise<Session.ISession> {
  152. return Kernel.connectTo(this.kernel.id, this.serverSettings).then(kernel => {
  153. return new DefaultSession({
  154. path: this._path,
  155. serverSettings: this.serverSettings
  156. }, this._id, kernel);
  157. });
  158. }
  159. /**
  160. * Update the session based on a session model from the server.
  161. */
  162. update(model: Session.IModel): Promise<void> {
  163. // Avoid a race condition if we are waiting for a REST call return.
  164. if (this._updating) {
  165. return Promise.resolve(void 0);
  166. }
  167. let oldModel = this.model;
  168. this._path = model.path;
  169. this._name = model.name;
  170. this._type = model.type;
  171. if (this._kernel.isDisposed || model.kernel.id !== this._kernel.id) {
  172. return Kernel.connectTo(model.kernel.id, this.serverSettings).then(kernel => {
  173. this.setupKernel(kernel);
  174. this._kernelChanged.emit(kernel);
  175. this._handleModelChange(oldModel);
  176. });
  177. }
  178. this._handleModelChange(oldModel);
  179. return Promise.resolve(void 0);
  180. }
  181. /**
  182. * Dispose of the resources held by the session.
  183. */
  184. dispose(): void {
  185. if (this.isDisposed) {
  186. return;
  187. }
  188. this._isDisposed = true;
  189. if (this._kernel) {
  190. this._kernel.dispose();
  191. }
  192. Private.removeRunning(this);
  193. this._kernel = null;
  194. Signal.clearData(this);
  195. }
  196. /**
  197. * Change the session path.
  198. *
  199. * @param path - The new session path.
  200. *
  201. * @returns A promise that resolves when the session has renamed.
  202. *
  203. * #### Notes
  204. * This uses the Jupyter REST API, and the response is validated.
  205. * The promise is fulfilled on a valid response and rejected otherwise.
  206. */
  207. setPath(path: string): Promise<void> {
  208. if (this.isDisposed) {
  209. return Promise.reject(new Error('Session is disposed'));
  210. }
  211. let data = JSON.stringify({ path });
  212. return this._patch(data).then(() => { return void 0; });
  213. }
  214. /**
  215. * Change the session name.
  216. */
  217. setName(name: string): Promise<void> {
  218. if (this.isDisposed) {
  219. return Promise.reject(new Error('Session is disposed'));
  220. }
  221. let data = JSON.stringify({ name });
  222. return this._patch(data).then(() => { return void 0; });
  223. }
  224. /**
  225. * Change the session type.
  226. */
  227. setType(type: string): Promise<void> {
  228. if (this.isDisposed) {
  229. return Promise.reject(new Error('Session is disposed'));
  230. }
  231. let data = JSON.stringify({ type });
  232. return this._patch(data).then(() => { return void 0; });
  233. }
  234. /**
  235. * Change the kernel.
  236. *
  237. * @params options - The name or id of the new kernel.
  238. *
  239. * #### Notes
  240. * This shuts down the existing kernel and creates a new kernel,
  241. * keeping the existing session ID and session path.
  242. */
  243. changeKernel(options: Kernel.IModel): Promise<Kernel.IKernelConnection> {
  244. if (this.isDisposed) {
  245. return Promise.reject(new Error('Session is disposed'));
  246. }
  247. let data = JSON.stringify({ kernel: options });
  248. if (this._kernel) {
  249. return this._kernel.ready.then(() => {
  250. this._kernel.dispose();
  251. return this._patch(data);
  252. }).then(() => this.kernel);
  253. }
  254. return this._patch(data).then(() => this.kernel);
  255. }
  256. /**
  257. * Kill the kernel and shutdown the session.
  258. *
  259. * @returns - The promise fulfilled on a valid response from the server.
  260. *
  261. * #### Notes
  262. * Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/sessions), and validates the response.
  263. * Emits a [sessionDied] signal on success.
  264. */
  265. shutdown(): Promise<void> {
  266. if (this.isDisposed) {
  267. return Promise.reject(new Error('Session is disposed'));
  268. }
  269. return Private.shutdownSession(this.id, this.serverSettings);
  270. }
  271. /**
  272. * Handle connections to a kernel.
  273. */
  274. protected setupKernel(kernel: Kernel.IKernel): void {
  275. this._kernel = kernel;
  276. kernel.statusChanged.connect(this.onKernelStatus, this);
  277. kernel.unhandledMessage.connect(this.onUnhandledMessage, this);
  278. kernel.iopubMessage.connect(this.onIOPubMessage, this);
  279. }
  280. /**
  281. * Handle to changes in the Kernel status.
  282. */
  283. protected onKernelStatus(sender: Kernel.IKernel, state: Kernel.Status) {
  284. this._statusChanged.emit(state);
  285. }
  286. /**
  287. * Handle iopub kernel messages.
  288. */
  289. protected onIOPubMessage(sender: Kernel.IKernel, msg: KernelMessage.IIOPubMessage) {
  290. this._iopubMessage.emit(msg);
  291. }
  292. /**
  293. * Handle unhandled kernel messages.
  294. */
  295. protected onUnhandledMessage(sender: Kernel.IKernel, msg: KernelMessage.IMessage) {
  296. this._unhandledMessage.emit(msg);
  297. }
  298. /**
  299. * Send a PATCH to the server, updating the session path or the kernel.
  300. */
  301. private _patch(data: string): Promise<Session.IModel> {
  302. this._updating = true;
  303. let settings = this.serverSettings;
  304. let request = {
  305. url: Private.getSessionUrl(settings.baseUrl, this._id),
  306. method: 'PATCH',
  307. data,
  308. cache: false
  309. };
  310. return ServerConnection.makeRequest(request, settings).then(response => {
  311. this._updating = false;
  312. if (response.xhr.status !== 200) {
  313. throw ServerConnection.makeError(response);
  314. }
  315. let value = response.data as Session.IModel;
  316. try {
  317. validate.validateModel(value);
  318. } catch (err) {
  319. throw ServerConnection.makeError(response, err.message);
  320. }
  321. return Private.updateFromServer(value, settings.baseUrl);
  322. }, error => {
  323. this._updating = false;
  324. return Private.onSessionError(error);
  325. });
  326. }
  327. /**
  328. * Handle a change to the model.
  329. */
  330. private _handleModelChange(oldModel: Session.IModel): void {
  331. if (oldModel.name !== this._name) {
  332. this._propertyChanged.emit('name');
  333. }
  334. if (oldModel.type !== this._type) {
  335. this._propertyChanged.emit('type');
  336. }
  337. if (oldModel.path !== this._path) {
  338. this._propertyChanged.emit('path');
  339. }
  340. }
  341. private _id = '';
  342. private _path = '';
  343. private _name = '';
  344. private _type = '';
  345. private _kernel: Kernel.IKernel = null;
  346. private _uuid = '';
  347. private _isDisposed = false;
  348. private _updating = false;
  349. private _kernelChanged = new Signal<this, Kernel.IKernelConnection>(this);
  350. private _statusChanged = new Signal<this, Kernel.Status>(this);
  351. private _iopubMessage = new Signal<this, KernelMessage.IIOPubMessage>(this);
  352. private _unhandledMessage = new Signal<this, KernelMessage.IMessage>(this);
  353. private _propertyChanged = new Signal<this, 'path' | 'name' | 'type'>(this);
  354. }
  355. /**
  356. * The namespace for `DefaultSession` statics.
  357. */
  358. export
  359. namespace DefaultSession {
  360. /**
  361. * List the running sessions.
  362. */
  363. export
  364. function listRunning(settings?: ServerConnection.ISettings): Promise<Session.IModel[]> {
  365. return Private.listRunning(settings);
  366. }
  367. /**
  368. * Start a new session.
  369. */
  370. export
  371. function startNew(options: Session.IOptions): Promise<Session.ISession> {
  372. return Private.startNew(options);
  373. }
  374. /**
  375. * Find a session by id.
  376. */
  377. export
  378. function findById(id: string, settings?: ServerConnection.ISettings): Promise<Session.IModel> {
  379. return Private.findById(id, settings);
  380. }
  381. /**
  382. * Find a session by path.
  383. */
  384. export
  385. function findByPath(path: string, settings?: ServerConnection.ISettings): Promise<Session.IModel> {
  386. return Private.findByPath(path, settings);
  387. }
  388. /**
  389. * Connect to a running session.
  390. */
  391. export
  392. function connectTo(id: string, settings?: ServerConnection.ISettings): Promise<Session.ISession> {
  393. return Private.connectTo(id, settings);
  394. }
  395. /**
  396. * Shut down a session by id.
  397. */
  398. export
  399. function shutdown(id: string, settings?: ServerConnection.ISettings): Promise<void> {
  400. return Private.shutdownSession(id, settings);
  401. }
  402. }
  403. /**
  404. * A namespace for session private data.
  405. */
  406. namespace Private {
  407. /**
  408. * The running sessions mapped by base url.
  409. */
  410. const runningSessions = new Map<string, DefaultSession[]>();
  411. /**
  412. * Add a session to the running sessions.
  413. */
  414. export
  415. function addRunning(session: DefaultSession): void {
  416. let running: DefaultSession[] = (
  417. runningSessions.get(session.serverSettings.baseUrl) || []
  418. );
  419. running.push(session);
  420. runningSessions.set(session.serverSettings.baseUrl, running);
  421. }
  422. /**
  423. * Remove a session from the running sessions.
  424. */
  425. export
  426. function removeRunning(session: DefaultSession): void {
  427. let running = runningSessions.get(session.serverSettings.baseUrl);
  428. ArrayExt.removeFirstOf(running, session);
  429. }
  430. /**
  431. * Connect to a running session.
  432. */
  433. export
  434. function connectTo(id: string, settings?: ServerConnection.ISettings): Promise<Session.ISession> {
  435. settings = settings || ServerConnection.makeSettings();
  436. let running = runningSessions.get(settings.baseUrl) || [];
  437. let session = find(running, value => value.id === id);
  438. if (session) {
  439. return Promise.resolve(session.clone());
  440. }
  441. return getSessionModel(id, settings).then(model => {
  442. return createSession(model, settings);
  443. }).catch(() => {
  444. let msg = `No running session with id: ${id}`;
  445. return typedThrow<Session.ISession>(msg);
  446. });
  447. }
  448. /**
  449. * Create a Session object.
  450. *
  451. * @returns - A promise that resolves with a started session.
  452. */
  453. export
  454. function createSession(model: Session.IModel, settings?: ServerConnection.ISettings): Promise<DefaultSession> {
  455. settings = settings || ServerConnection.makeSettings();
  456. return Kernel.connectTo(model.kernel.id, settings).then(kernel => {
  457. return new DefaultSession({
  458. path: model.path,
  459. type: model.type,
  460. name: model.name,
  461. serverSettings: settings
  462. }, model.id, kernel);
  463. }).catch(error => {
  464. return typedThrow('Session failed to start: ' + error.message);
  465. });
  466. }
  467. /**
  468. * Find a session by id.
  469. */
  470. export
  471. function findById(id: string, settings?: ServerConnection.ISettings): Promise<Session.IModel> {
  472. settings = settings || ServerConnection.makeSettings();
  473. let running = runningSessions.get(settings.baseUrl) || [];
  474. let session = find(running, value => value.id === id);
  475. if (session) {
  476. return Promise.resolve(session.model);
  477. }
  478. return getSessionModel(id, settings).catch(() => {
  479. let msg = `No running session for id: ${id}`;
  480. return typedThrow<Session.IModel>(msg);
  481. });
  482. }
  483. /**
  484. * Find a session by path.
  485. */
  486. export
  487. function findByPath(path: string, settings?: ServerConnection.ISettings): Promise<Session.IModel> {
  488. settings = settings || ServerConnection.makeSettings();
  489. let running = runningSessions.get(settings.baseUrl) || [];
  490. let session = find(running, value => value.path === path);
  491. if (session) {
  492. return Promise.resolve(session.model);
  493. }
  494. return listRunning(settings).then(models => {
  495. let model = find(models, value => {
  496. return value.path === path;
  497. });
  498. if (model) {
  499. return model;
  500. }
  501. let msg = `No running session for path: ${path}`;
  502. return typedThrow<Session.IModel>(msg);
  503. });
  504. }
  505. /**
  506. * Get a full session model from the server by session id string.
  507. */
  508. export
  509. function getSessionModel(id: string, settings?: ServerConnection.ISettings): Promise<Session.IModel> {
  510. settings = settings || ServerConnection.makeSettings();
  511. let request = {
  512. url: getSessionUrl(settings.baseUrl, id),
  513. method: 'GET',
  514. cache: false
  515. };
  516. return ServerConnection.makeRequest(request, settings).then(response => {
  517. if (response.xhr.status !== 200) {
  518. throw ServerConnection.makeError(response);
  519. }
  520. let data = response.data as Session.IModel;
  521. try {
  522. validate.validateModel(data);
  523. } catch (err) {
  524. throw ServerConnection.makeError(response, err.message);
  525. }
  526. return updateFromServer(data, settings.baseUrl);
  527. }, Private.onSessionError);
  528. }
  529. /**
  530. * Get a session url.
  531. */
  532. export
  533. function getSessionUrl(baseUrl: string, id: string): string {
  534. return URLExt.join(baseUrl, SESSION_SERVICE_URL, id);
  535. }
  536. /**
  537. * Kill the sessions by id.
  538. */
  539. function killSessions(id: string, baseUrl: string): void {
  540. let running = runningSessions.get(baseUrl) || [];
  541. each(running.slice(), session => {
  542. if (session.id === id) {
  543. session.terminated.emit(void 0);
  544. session.dispose();
  545. }
  546. });
  547. }
  548. /**
  549. * List the running sessions.
  550. */
  551. export
  552. function listRunning(settings?: ServerConnection.ISettings): Promise<Session.IModel[]> {
  553. settings = settings || ServerConnection.makeSettings();
  554. let request = {
  555. url: URLExt.join(settings.baseUrl, SESSION_SERVICE_URL),
  556. method: 'GET',
  557. cache: false
  558. };
  559. return ServerConnection.makeRequest(request, settings).then(response => {
  560. if (response.xhr.status !== 200) {
  561. throw ServerConnection.makeError(response);
  562. }
  563. let data = response.data as Session.IModel[];
  564. if (!Array.isArray(response.data)) {
  565. throw ServerConnection.makeError(response, 'Invalid Session list');
  566. }
  567. for (let i = 0; i < data.length; i++) {
  568. try {
  569. validate.validateModel(data[i]);
  570. } catch (err) {
  571. throw ServerConnection.makeError(response, err.message);
  572. }
  573. }
  574. return updateRunningSessions(data, settings.baseUrl);
  575. }, Private.onSessionError);
  576. }
  577. /**
  578. * Handle an error on a session Ajax call.
  579. */
  580. export
  581. function onSessionError(error: ServerConnection.IError): Promise<any> {
  582. let text = (error.message ||
  583. error.xhr.statusText ||
  584. error.xhr.responseText);
  585. let msg = `API request failed: ${text}`;
  586. console.error(msg);
  587. return Promise.reject(error);
  588. }
  589. /**
  590. * Shut down a session by id.
  591. */
  592. export
  593. function shutdownSession(id: string, settings?: ServerConnection.ISettings): Promise<void> {
  594. settings = settings || ServerConnection.makeSettings();
  595. let request = {
  596. url: getSessionUrl(settings.baseUrl, id),
  597. method: 'DELETE',
  598. cache: false
  599. };
  600. return ServerConnection.makeRequest(request, settings).then(response => {
  601. if (response.xhr.status !== 204) {
  602. throw ServerConnection.makeError(response);
  603. }
  604. killSessions(id, settings.baseUrl);
  605. }, err => {
  606. if (err.xhr.status === 404) {
  607. let response = JSON.parse(err.xhr.responseText) as any;
  608. console.warn(response['message']);
  609. killSessions(id, settings.baseUrl);
  610. return;
  611. }
  612. if (err.xhr.status === 410) {
  613. err.message = 'The kernel was deleted but the session was not';
  614. }
  615. return onSessionError(err);
  616. });
  617. }
  618. /**
  619. * Start a new session.
  620. */
  621. export
  622. function startNew(options: Session.IOptions): Promise<Session.ISession> {
  623. if (options.path === void 0) {
  624. return Promise.reject(new Error('Must specify a path'));
  625. }
  626. return startSession(options).then(model => {
  627. return createSession(model, options.serverSettings);
  628. });
  629. }
  630. /**
  631. * Create a new session, or return an existing session if a session if
  632. * the session path already exists
  633. */
  634. export
  635. function startSession(options: Session.IOptions): Promise<Session.IModel> {
  636. let settings = options.serverSettings || ServerConnection.makeSettings();
  637. let model = {
  638. kernel: { name: options.kernelName, id: options.kernelId },
  639. path: options.path,
  640. type: options.type || '',
  641. name: options.name || ''
  642. };
  643. let request = {
  644. url: URLExt.join(settings.baseUrl, SESSION_SERVICE_URL),
  645. method: 'POST',
  646. cache: false,
  647. data: JSON.stringify(model)
  648. };
  649. return ServerConnection.makeRequest(request, settings).then(response => {
  650. if (response.xhr.status !== 201) {
  651. throw ServerConnection.makeError(response);
  652. }
  653. try {
  654. validate.validateModel(response.data);
  655. } catch (err) {
  656. throw ServerConnection.makeError(response, err.message);
  657. }
  658. let data = response.data as Session.IModel;
  659. return updateFromServer(data, settings.baseUrl);
  660. }, onSessionError);
  661. }
  662. /**
  663. * Throw a typed error.
  664. */
  665. export
  666. function typedThrow<T>(msg: string): T {
  667. throw new Error(msg);
  668. }
  669. /**
  670. * Update the running sessions given an updated session Id.
  671. */
  672. export
  673. function updateFromServer(model: Session.IModel, baseUrl: string): Promise<Session.IModel> {
  674. let promises: Promise<void>[] = [];
  675. let running = runningSessions.get(baseUrl) || [];
  676. each(running.slice(), session => {
  677. if (session.id === model.id) {
  678. promises.push(session.update(model));
  679. }
  680. });
  681. return Promise.all(promises).then(() => { return model; });
  682. }
  683. /**
  684. * Update the running sessions based on new data from the server.
  685. */
  686. export
  687. function updateRunningSessions(sessions: Session.IModel[], baseUrl: string): Promise<Session.IModel[]> {
  688. let promises: Promise<void>[] = [];
  689. let running = runningSessions.get(baseUrl) || [];
  690. each(running.slice(), session => {
  691. let updated = find(sessions, sId => {
  692. if (session.id === sId.id) {
  693. promises.push(session.update(sId));
  694. return true;
  695. }
  696. });
  697. // If session is no longer running on disk, emit dead signal.
  698. if (!updated && session.status !== 'dead') {
  699. session.terminated.emit(void 0);
  700. }
  701. });
  702. return Promise.all(promises).then(() => { return sessions; });
  703. }
  704. }