1 | ;
|
2 | // Copyright (c) Jupyter Development Team.
|
3 | // Distributed under the terms of the Modified BSD License.
|
4 | Object.defineProperty(exports, "__esModule", { value: true });
|
5 | exports.KernelManager = void 0;
|
6 | const polling_1 = require("@lumino/polling");
|
7 | const signaling_1 = require("@lumino/signaling");
|
8 | const __1 = require("..");
|
9 | const basemanager_1 = require("../basemanager");
|
10 | const restapi_1 = require("./restapi");
|
11 | const default_1 = require("./default");
|
12 | /**
|
13 | * An implementation of a kernel manager.
|
14 | */
|
15 | class KernelManager extends basemanager_1.BaseManager {
|
16 | /**
|
17 | * Construct a new kernel manager.
|
18 | *
|
19 | * @param options - The default options for kernel.
|
20 | */
|
21 | constructor(options = {}) {
|
22 | var _a;
|
23 | super(options);
|
24 | this._isReady = false;
|
25 | this._kernelConnections = new Set();
|
26 | this._models = new Map();
|
27 | this._runningChanged = new signaling_1.Signal(this);
|
28 | this._connectionFailure = new signaling_1.Signal(this);
|
29 | // Start model and specs polling with exponential backoff.
|
30 | this._pollModels = new polling_1.Poll({
|
31 | auto: false,
|
32 | factory: () => this.requestRunning(),
|
33 | frequency: {
|
34 | interval: 10 * 1000,
|
35 | backoff: true,
|
36 | max: 300 * 1000
|
37 | },
|
38 | name: `@jupyterlab/services:KernelManager#models`,
|
39 | standby: (_a = options.standby) !== null && _a !== void 0 ? _a : 'when-hidden'
|
40 | });
|
41 | // Initialize internal data.
|
42 | this._ready = (async () => {
|
43 | await this._pollModels.start();
|
44 | await this._pollModels.tick;
|
45 | this._isReady = true;
|
46 | })();
|
47 | }
|
48 | /**
|
49 | * Test whether the manager is ready.
|
50 | */
|
51 | get isReady() {
|
52 | return this._isReady;
|
53 | }
|
54 | /**
|
55 | * A promise that fulfills when the manager is ready.
|
56 | */
|
57 | get ready() {
|
58 | return this._ready;
|
59 | }
|
60 | /**
|
61 | * A signal emitted when the running kernels change.
|
62 | */
|
63 | get runningChanged() {
|
64 | return this._runningChanged;
|
65 | }
|
66 | /**
|
67 | * A signal emitted when there is a connection failure.
|
68 | */
|
69 | get connectionFailure() {
|
70 | return this._connectionFailure;
|
71 | }
|
72 | /**
|
73 | * Dispose of the resources used by the manager.
|
74 | */
|
75 | dispose() {
|
76 | if (this.isDisposed) {
|
77 | return;
|
78 | }
|
79 | this._models.clear();
|
80 | this._kernelConnections.forEach(x => x.dispose());
|
81 | this._pollModels.dispose();
|
82 | super.dispose();
|
83 | }
|
84 | /**
|
85 | * Connect to an existing kernel.
|
86 | *
|
87 | * @returns The new kernel connection.
|
88 | *
|
89 | * #### Notes
|
90 | * This will use the manager's server settings and ignore any server
|
91 | * settings passed in the options.
|
92 | */
|
93 | connectTo(options) {
|
94 | var _a;
|
95 | const { id } = options.model;
|
96 | let handleComms = (_a = options.handleComms) !== null && _a !== void 0 ? _a : true;
|
97 | // By default, handle comms only if no other kernel connection is.
|
98 | if (options.handleComms === undefined) {
|
99 | for (const kc of this._kernelConnections) {
|
100 | if (kc.id === id && kc.handleComms) {
|
101 | handleComms = false;
|
102 | break;
|
103 | }
|
104 | }
|
105 | }
|
106 | const kernelConnection = new default_1.KernelConnection({
|
107 | handleComms,
|
108 | ...options,
|
109 | serverSettings: this.serverSettings
|
110 | });
|
111 | this._onStarted(kernelConnection);
|
112 | if (!this._models.has(id)) {
|
113 | // We trust the user to connect to an existing kernel, but we verify
|
114 | // asynchronously.
|
115 | void this.refreshRunning().catch(() => {
|
116 | /* no-op */
|
117 | });
|
118 | }
|
119 | return kernelConnection;
|
120 | }
|
121 | /**
|
122 | * Create an iterator over the most recent running kernels.
|
123 | *
|
124 | * @returns A new iterator over the running kernels.
|
125 | */
|
126 | running() {
|
127 | return this._models.values();
|
128 | }
|
129 | /**
|
130 | * Force a refresh of the running kernels.
|
131 | *
|
132 | * @returns A promise that resolves when the running list has been refreshed.
|
133 | *
|
134 | * #### Notes
|
135 | * This is not typically meant to be called by the user, since the
|
136 | * manager maintains its own internal state.
|
137 | */
|
138 | async refreshRunning() {
|
139 | await this._pollModels.refresh();
|
140 | await this._pollModels.tick;
|
141 | }
|
142 | /**
|
143 | * Start a new kernel.
|
144 | *
|
145 | * @param createOptions - The kernel creation options
|
146 | *
|
147 | * @param connectOptions - The kernel connection options
|
148 | *
|
149 | * @returns A promise that resolves with the kernel connection.
|
150 | *
|
151 | * #### Notes
|
152 | * The manager `serverSettings` will be always be used.
|
153 | */
|
154 | async startNew(createOptions = {}, connectOptions = {}) {
|
155 | const model = await (0, restapi_1.startNew)(createOptions, this.serverSettings);
|
156 | return this.connectTo({
|
157 | ...connectOptions,
|
158 | model
|
159 | });
|
160 | }
|
161 | /**
|
162 | * Shut down a kernel by id.
|
163 | *
|
164 | * @param id - The id of the target kernel.
|
165 | *
|
166 | * @returns A promise that resolves when the operation is complete.
|
167 | */
|
168 | async shutdown(id) {
|
169 | await (0, restapi_1.shutdownKernel)(id, this.serverSettings);
|
170 | await this.refreshRunning();
|
171 | }
|
172 | /**
|
173 | * Shut down all kernels.
|
174 | *
|
175 | * @returns A promise that resolves when all of the kernels are shut down.
|
176 | */
|
177 | async shutdownAll() {
|
178 | // Update the list of models to make sure our list is current.
|
179 | await this.refreshRunning();
|
180 | // Shut down all models.
|
181 | await Promise.all([...this._models.keys()].map(id => (0, restapi_1.shutdownKernel)(id, this.serverSettings)));
|
182 | // Update the list of models to clear out our state.
|
183 | await this.refreshRunning();
|
184 | }
|
185 | /**
|
186 | * Find a kernel by id.
|
187 | *
|
188 | * @param id - The id of the target kernel.
|
189 | *
|
190 | * @returns A promise that resolves with the kernel's model.
|
191 | */
|
192 | async findById(id) {
|
193 | if (this._models.has(id)) {
|
194 | return this._models.get(id);
|
195 | }
|
196 | await this.refreshRunning();
|
197 | return this._models.get(id);
|
198 | }
|
199 | /**
|
200 | * Execute a request to the server to poll running kernels and update state.
|
201 | */
|
202 | async requestRunning() {
|
203 | var _a, _b;
|
204 | let models;
|
205 | try {
|
206 | models = await (0, restapi_1.listRunning)(this.serverSettings);
|
207 | }
|
208 | catch (err) {
|
209 | // Handle network errors, as well as cases where we are on a
|
210 | // JupyterHub and the server is not running. JupyterHub returns a
|
211 | // 503 (<2.0) or 424 (>2.0) in that case.
|
212 | if (err instanceof __1.ServerConnection.NetworkError ||
|
213 | ((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 503 ||
|
214 | ((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 424) {
|
215 | this._connectionFailure.emit(err);
|
216 | }
|
217 | throw err;
|
218 | }
|
219 | if (this.isDisposed) {
|
220 | return;
|
221 | }
|
222 | if (this._models.size === models.length &&
|
223 | models.every(model => {
|
224 | const existing = this._models.get(model.id);
|
225 | if (!existing) {
|
226 | return false;
|
227 | }
|
228 | return (existing.connections === model.connections &&
|
229 | existing.execution_state === model.execution_state &&
|
230 | existing.last_activity === model.last_activity &&
|
231 | existing.name === model.name &&
|
232 | existing.reason === model.reason &&
|
233 | existing.traceback === model.traceback);
|
234 | })) {
|
235 | // Identical models list (presuming models does not contain duplicate
|
236 | // ids), so just return
|
237 | return;
|
238 | }
|
239 | this._models = new Map(models.map(x => [x.id, x]));
|
240 | // For any kernel connection to a kernel that doesn't exist, notify it of
|
241 | // the shutdown.
|
242 | this._kernelConnections.forEach(kc => {
|
243 | if (!this._models.has(kc.id)) {
|
244 | kc.handleShutdown();
|
245 | }
|
246 | });
|
247 | this._runningChanged.emit(models);
|
248 | }
|
249 | /**
|
250 | * Handle a kernel starting.
|
251 | */
|
252 | _onStarted(kernelConnection) {
|
253 | this._kernelConnections.add(kernelConnection);
|
254 | kernelConnection.statusChanged.connect(this._onStatusChanged, this);
|
255 | kernelConnection.disposed.connect(this._onDisposed, this);
|
256 | }
|
257 | _onDisposed(kernelConnection) {
|
258 | this._kernelConnections.delete(kernelConnection);
|
259 | // A dispose emission could mean the server session is deleted, or that
|
260 | // the kernel JS object is disposed and the kernel still exists on the
|
261 | // server, so we refresh from the server to make sure we reflect the
|
262 | // server state.
|
263 | void this.refreshRunning().catch(() => {
|
264 | /* no-op */
|
265 | });
|
266 | }
|
267 | _onStatusChanged(kernelConnection, status) {
|
268 | if (status === 'dead') {
|
269 | // We asynchronously update our list of kernels, which asynchronously
|
270 | // will dispose them. We do not want to immediately dispose them because
|
271 | // there may be other signal handlers that want to be called.
|
272 | void this.refreshRunning().catch(() => {
|
273 | /* no-op */
|
274 | });
|
275 | }
|
276 | }
|
277 | }
|
278 | exports.KernelManager = KernelManager;
|
279 | /**
|
280 | * The namespace for `KernelManager` class statics.
|
281 | */
|
282 | (function (KernelManager) {
|
283 | /**
|
284 | * A no-op kernel manager to be used when starting kernels.
|
285 | */
|
286 | class NoopManager extends KernelManager {
|
287 | constructor() {
|
288 | super(...arguments);
|
289 | this._readyPromise = new Promise(() => {
|
290 | /* no-op */
|
291 | });
|
292 | }
|
293 | /**
|
294 | * Whether the manager is active.
|
295 | */
|
296 | get isActive() {
|
297 | return false;
|
298 | }
|
299 | /**
|
300 | * Used for testing.
|
301 | */
|
302 | get parentReady() {
|
303 | return super.ready;
|
304 | }
|
305 | /**
|
306 | * Start a new kernel - throws an error since it is not supported.
|
307 | */
|
308 | async startNew(createOptions = {}, connectOptions = {}) {
|
309 | return Promise.reject(new Error('Not implemented in no-op Kernel Manager'));
|
310 | }
|
311 | /**
|
312 | * Connect to an existing kernel - throws an error since it is not supported.
|
313 | */
|
314 | connectTo(options) {
|
315 | throw new Error('Not implemented in no-op Kernel Manager');
|
316 | }
|
317 | /**
|
318 | * Shut down a kernel by id - throws an error since it is not supported.
|
319 | */
|
320 | async shutdown(id) {
|
321 | return Promise.reject(new Error('Not implemented in no-op Kernel Manager'));
|
322 | }
|
323 | /**
|
324 | * A promise that fulfills when the manager is ready (never).
|
325 | */
|
326 | get ready() {
|
327 | return this.parentReady.then(() => this._readyPromise);
|
328 | }
|
329 | /**
|
330 | * Execute a request to the server to poll running kernels and update state.
|
331 | */
|
332 | async requestRunning() {
|
333 | return Promise.resolve();
|
334 | }
|
335 | }
|
336 | KernelManager.NoopManager = NoopManager;
|
337 | })(KernelManager || (exports.KernelManager = KernelManager = {}));
|
338 | //# sourceMappingURL=manager.js.map |
\ | No newline at end of file |