UNPKG

@jupyterlab/services

Version:

Client APIs for the Jupyter services REST APIs

348 lines 11.8 kB
"use strict"; // Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. Object.defineProperty(exports, "__esModule", { value: true }); exports.SessionManager = void 0; const polling_1 = require("@lumino/polling"); const signaling_1 = require("@lumino/signaling"); const serverconnection_1 = require("../serverconnection"); const basemanager_1 = require("../basemanager"); const default_1 = require("./default"); const restapi_1 = require("./restapi"); /** * An implementation of a session manager. */ class SessionManager extends basemanager_1.BaseManager { /** * Construct a new session manager. * * @param options - The default options for each session. */ constructor(options) { var _a; super(options); this._isReady = false; this._sessionConnections = new Set(); this._models = new Map(); this._runningChanged = new signaling_1.Signal(this); this._connectionFailure = new signaling_1.Signal(this); // We define these here so they bind `this` correctly this._connectToKernel = (options) => { return this._kernelManager.connectTo(options); }; this._kernelManager = options.kernelManager; // Start model polling with exponential backoff. this._pollModels = new polling_1.Poll({ auto: false, factory: () => this.requestRunning(), frequency: { interval: 10 * 1000, backoff: true, max: 300 * 1000 }, name: `@jupyterlab/services:SessionManager#models`, standby: (_a = options.standby) !== null && _a !== void 0 ? _a : 'when-hidden' }); // Initialize internal data. this._ready = (async () => { await this._pollModels.start(); await this._pollModels.tick; if (this._kernelManager.isActive) { await this._kernelManager.ready; } this._isReady = true; })(); } /** * Test whether the manager is ready. */ get isReady() { return this._isReady; } /** * A promise that fulfills when the manager is ready. */ get ready() { return this._ready; } /** * A signal emitted when the running sessions change. */ get runningChanged() { return this._runningChanged; } /** * A signal emitted when there is a connection failure. */ get connectionFailure() { return this._connectionFailure; } /** * Dispose of the resources used by the manager. */ dispose() { if (this.isDisposed) { return; } this._models.clear(); this._sessionConnections.forEach(x => x.dispose()); this._pollModels.dispose(); super.dispose(); } /* * Connect to a running session. See also [[connectToSession]]. */ connectTo(options) { const sessionConnection = new default_1.SessionConnection({ ...options, connectToKernel: this._connectToKernel, serverSettings: this.serverSettings }); this._onStarted(sessionConnection); if (!this._models.has(options.model.id)) { // We trust the user to connect to an existing session, but we verify // asynchronously. void this.refreshRunning().catch(() => { /* no-op */ }); } return sessionConnection; } /** * Create an iterator over the most recent running sessions. * * @returns A new iterator over the running sessions. */ running() { return this._models.values(); } /** * Force a refresh of the running sessions. * * @returns A promise that with the list of running sessions. * * #### Notes * This is not typically meant to be called by the user, since the * manager maintains its own internal state. */ async refreshRunning() { await this._pollModels.refresh(); await this._pollModels.tick; } /** * Start a new session. See also [[startNewSession]]. * * @param createOptions - Options for creating the session * * @param connectOptions - Options for connecting to the session */ async startNew(createOptions, connectOptions = {}) { const model = await (0, restapi_1.startSession)(createOptions, this.serverSettings); await this.refreshRunning(); return this.connectTo({ ...connectOptions, model }); } /** * Shut down a session by id. */ async shutdown(id) { await (0, restapi_1.shutdownSession)(id, this.serverSettings); await this.refreshRunning(); } /** * Shut down all sessions. * * @returns A promise that resolves when all of the kernels are shut down. */ async shutdownAll() { // Update the list of models to make sure our list is current. await this.refreshRunning(); // Shut down all models. await Promise.all([...this._models.keys()].map(id => (0, restapi_1.shutdownSession)(id, this.serverSettings))); // Update the list of models to clear out our state. await this.refreshRunning(); } /** * Find a session associated with a path and stop it if it is the only session * using that kernel. * * @param path - The path in question. * * @returns A promise that resolves when the relevant sessions are stopped. */ async stopIfNeeded(path) { try { const sessions = await (0, restapi_1.listRunning)(this.serverSettings); const matches = sessions.filter(value => value.path === path); if (matches.length === 1) { const id = matches[0].id; await this.shutdown(id); } } catch (error) { /* Always succeed. */ } } /** * Find a session by id. */ async findById(id) { if (this._models.has(id)) { return this._models.get(id); } await this.refreshRunning(); return this._models.get(id); } /** * Find a session by path. */ async findByPath(path) { for (const m of this._models.values()) { if (m.path === path) { return m; } } await this.refreshRunning(); for (const m of this._models.values()) { if (m.path === path) { return m; } } return undefined; } /** * Execute a request to the server to poll running kernels and update state. */ async requestRunning() { var _a, _b; let models; try { models = await (0, restapi_1.listRunning)(this.serverSettings); } catch (err) { // Handle network errors, as well as cases where we are on a // JupyterHub and the server is not running. JupyterHub returns a // 503 (<2.0) or 424 (>2.0) in that case. if (err instanceof serverconnection_1.ServerConnection.NetworkError || ((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 503 || ((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 424) { this._connectionFailure.emit(err); } throw err; } if (this.isDisposed) { return; } if (this._models.size === models.length && models.every(model => { var _a, _b, _c, _d; const existing = this._models.get(model.id); if (!existing) { return false; } return (((_a = existing.kernel) === null || _a === void 0 ? void 0 : _a.id) === ((_b = model.kernel) === null || _b === void 0 ? void 0 : _b.id) && ((_c = existing.kernel) === null || _c === void 0 ? void 0 : _c.name) === ((_d = model.kernel) === null || _d === void 0 ? void 0 : _d.name) && existing.name === model.name && existing.path === model.path && existing.type === model.type); })) { // Identical models list (presuming models does not contain duplicate // ids), so just return return; } this._models = new Map(models.map(x => [x.id, x])); this._sessionConnections.forEach(sc => { if (this._models.has(sc.id)) { sc.update(this._models.get(sc.id)); } else { sc.dispose(); } }); this._runningChanged.emit(models); } /** * Handle a session starting. */ _onStarted(sessionConnection) { this._sessionConnections.add(sessionConnection); sessionConnection.disposed.connect(this._onDisposed, this); sessionConnection.propertyChanged.connect(this._onChanged, this); sessionConnection.kernelChanged.connect(this._onChanged, this); } _onDisposed(sessionConnection) { this._sessionConnections.delete(sessionConnection); // A session termination emission could mean the server session is deleted, // or that the session JS object is disposed and the session still exists on // the server, so we refresh from the server to make sure we reflect the // server state. void this.refreshRunning().catch(() => { /* no-op */ }); } _onChanged() { void this.refreshRunning().catch(() => { /* no-op */ }); } } exports.SessionManager = SessionManager; /** * The namespace for `SessionManager` class statics. */ (function (SessionManager) { /** * A no-op session manager to be used when starting sessions is not supported. */ class NoopManager extends SessionManager { constructor() { super(...arguments); this._readyPromise = new Promise(() => { /* no-op */ }); } /** * Whether the manager is active. */ get isActive() { return false; } /** * Used for testing. */ get parentReady() { return super.ready; } /** * Start a new session - throw an error since it is not supported. */ async startNew(createOptions, connectOptions = {}) { return Promise.reject(new Error('Not implemented in no-op Session Manager')); } /* * Connect to a running session - throw an error since it is not supported. */ connectTo(options) { throw Error('Not implemented in no-op Session Manager'); } /** * A promise that fulfills when the manager is ready (never). */ get ready() { return this.parentReady.then(() => this._readyPromise); } /** * Shut down a session by id - throw an error since it is not supported. */ async shutdown(id) { return Promise.reject(new Error('Not implemented in no-op Session Manager')); } /** * Execute a request to the server to poll running sessions and update state. */ async requestRunning() { return Promise.resolve(); } } SessionManager.NoopManager = NoopManager; })(SessionManager || (exports.SessionManager = SessionManager = {})); //# sourceMappingURL=manager.js.map