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