1 | ;
|
2 | // Copyright (c) Jupyter Development Team.
|
3 | // Distributed under the terms of the Modified BSD License.
|
4 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
5 | if (k2 === undefined) k2 = k;
|
6 | var desc = Object.getOwnPropertyDescriptor(m, k);
|
7 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
8 | desc = { enumerable: true, get: function() { return m[k]; } };
|
9 | }
|
10 | Object.defineProperty(o, k2, desc);
|
11 | }) : (function(o, m, k, k2) {
|
12 | if (k2 === undefined) k2 = k;
|
13 | o[k2] = m[k];
|
14 | }));
|
15 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
16 | Object.defineProperty(o, "default", { enumerable: true, value: v });
|
17 | }) : function(o, v) {
|
18 | o["default"] = v;
|
19 | });
|
20 | var __importStar = (this && this.__importStar) || function (mod) {
|
21 | if (mod && mod.__esModule) return mod;
|
22 | var result = {};
|
23 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
24 | __setModuleDefault(result, mod);
|
25 | return result;
|
26 | };
|
27 | Object.defineProperty(exports, "__esModule", { value: true });
|
28 | exports.KernelShellFutureHandler = exports.KernelControlFutureHandler = exports.KernelFutureHandler = void 0;
|
29 | const coreutils_1 = require("@lumino/coreutils");
|
30 | const disposable_1 = require("@lumino/disposable");
|
31 | const KernelMessage = __importStar(require("./messages"));
|
32 | /**
|
33 | * Implementation of a kernel future.
|
34 | *
|
35 | * If a reply is expected, the Future is considered done when both a `reply`
|
36 | * message and an `idle` iopub status message have been received. Otherwise, it
|
37 | * is considered done when the `idle` status is received.
|
38 | *
|
39 | */
|
40 | class KernelFutureHandler extends disposable_1.DisposableDelegate {
|
41 | /**
|
42 | * Construct a new KernelFutureHandler.
|
43 | */
|
44 | constructor(cb, msg, expectReply, disposeOnDone, kernel) {
|
45 | super(cb);
|
46 | this._status = 0;
|
47 | this._stdin = Private.noOp;
|
48 | this._iopub = Private.noOp;
|
49 | this._reply = Private.noOp;
|
50 | this._done = new coreutils_1.PromiseDelegate();
|
51 | this._hooks = new Private.HookList();
|
52 | this._disposeOnDone = true;
|
53 | this._msg = msg;
|
54 | if (!expectReply) {
|
55 | this._setFlag(Private.KernelFutureFlag.GotReply);
|
56 | }
|
57 | this._disposeOnDone = disposeOnDone;
|
58 | this._kernel = kernel;
|
59 | }
|
60 | /**
|
61 | * Get the original outgoing message.
|
62 | */
|
63 | get msg() {
|
64 | return this._msg;
|
65 | }
|
66 | /**
|
67 | * A promise that resolves when the future is done.
|
68 | */
|
69 | get done() {
|
70 | return this._done.promise;
|
71 | }
|
72 | /**
|
73 | * Get the reply handler.
|
74 | */
|
75 | get onReply() {
|
76 | return this._reply;
|
77 | }
|
78 | /**
|
79 | * Set the reply handler.
|
80 | */
|
81 | set onReply(cb) {
|
82 | this._reply = cb;
|
83 | }
|
84 | /**
|
85 | * Get the iopub handler.
|
86 | */
|
87 | get onIOPub() {
|
88 | return this._iopub;
|
89 | }
|
90 | /**
|
91 | * Set the iopub handler.
|
92 | */
|
93 | set onIOPub(cb) {
|
94 | this._iopub = cb;
|
95 | }
|
96 | /**
|
97 | * Get the stdin handler.
|
98 | */
|
99 | get onStdin() {
|
100 | return this._stdin;
|
101 | }
|
102 | /**
|
103 | * Set the stdin handler.
|
104 | */
|
105 | set onStdin(cb) {
|
106 | this._stdin = cb;
|
107 | }
|
108 | /**
|
109 | * Register hook for IOPub messages.
|
110 | *
|
111 | * @param hook - The callback invoked for an IOPub message.
|
112 | *
|
113 | * #### Notes
|
114 | * The IOPub hook system allows you to preempt the handlers for IOPub
|
115 | * messages handled by the future.
|
116 | *
|
117 | * The most recently registered hook is run first. A hook can return a
|
118 | * boolean or a promise to a boolean, in which case all kernel message
|
119 | * processing pauses until the promise is fulfilled. If a hook return value
|
120 | * resolves to false, any later hooks will not run and the function will
|
121 | * return a promise resolving to false. If a hook throws an error, the error
|
122 | * is logged to the console and the next hook is run. If a hook is
|
123 | * registered during the hook processing, it will not run until the next
|
124 | * message. If a hook is removed during the hook processing, it will be
|
125 | * deactivated immediately.
|
126 | */
|
127 | registerMessageHook(hook) {
|
128 | if (this.isDisposed) {
|
129 | throw new Error('Kernel future is disposed');
|
130 | }
|
131 | this._hooks.add(hook);
|
132 | }
|
133 | /**
|
134 | * Remove a hook for IOPub messages.
|
135 | *
|
136 | * @param hook - The hook to remove.
|
137 | *
|
138 | * #### Notes
|
139 | * If a hook is removed during the hook processing, it will be deactivated immediately.
|
140 | */
|
141 | removeMessageHook(hook) {
|
142 | if (this.isDisposed) {
|
143 | return;
|
144 | }
|
145 | this._hooks.remove(hook);
|
146 | }
|
147 | /**
|
148 | * Send an `input_reply` message.
|
149 | */
|
150 | sendInputReply(content, parent_header) {
|
151 | this._kernel.sendInputReply(content, parent_header);
|
152 | }
|
153 | /**
|
154 | * Dispose and unregister the future.
|
155 | */
|
156 | dispose() {
|
157 | this._stdin = Private.noOp;
|
158 | this._iopub = Private.noOp;
|
159 | this._reply = Private.noOp;
|
160 | this._hooks = null;
|
161 | if (!this._testFlag(Private.KernelFutureFlag.IsDone)) {
|
162 | // TODO: Uncomment the following logging code, and check for any tests that trigger it.
|
163 | // let status = [];
|
164 | // if (!this._testFlag(Private.KernelFutureFlag.GotIdle)) {
|
165 | // status.push('idle');
|
166 | // }
|
167 | // if (!this._testFlag(Private.KernelFutureFlag.GotReply)) {
|
168 | // status.push('reply');
|
169 | // }
|
170 | // console.warn(
|
171 | // `*************** DISPOSED BEFORE DONE: K${this._kernel.id.slice(
|
172 | // 0,
|
173 | // 6
|
174 | // )} M${this._msg.header.msg_id.slice(0, 6)} missing ${status.join(' ')}`
|
175 | // );
|
176 | // Reject the `done` promise, but catch its error here in case no one else
|
177 | // is waiting for the promise to resolve. This prevents the error from
|
178 | // being displayed in the console, but does not prevent it from being
|
179 | // caught by a client who is waiting for it.
|
180 | // Note: any `.then` and `.finally` attached to the `done` promise
|
181 | // will cause the error to be thrown as uncaught anyways.
|
182 | this._done.promise.catch(() => {
|
183 | /* no-op */
|
184 | });
|
185 | this._done.reject(new Error(`Canceled future for ${this.msg.header.msg_type} message before replies were done`));
|
186 | }
|
187 | super.dispose();
|
188 | }
|
189 | /**
|
190 | * Handle an incoming kernel message.
|
191 | */
|
192 | async handleMsg(msg) {
|
193 | switch (msg.channel) {
|
194 | case 'control':
|
195 | case 'shell':
|
196 | if (msg.channel === this.msg.channel &&
|
197 | msg.parent_header.msg_id === this.msg.header.msg_id) {
|
198 | await this._handleReply(msg);
|
199 | }
|
200 | break;
|
201 | case 'stdin':
|
202 | await this._handleStdin(msg);
|
203 | break;
|
204 | case 'iopub':
|
205 | await this._handleIOPub(msg);
|
206 | break;
|
207 | default:
|
208 | break;
|
209 | }
|
210 | }
|
211 | async _handleReply(msg) {
|
212 | const reply = this._reply;
|
213 | if (reply) {
|
214 | // tslint:disable-next-line:await-promise
|
215 | await reply(msg);
|
216 | }
|
217 | this._replyMsg = msg;
|
218 | this._setFlag(Private.KernelFutureFlag.GotReply);
|
219 | if (this._testFlag(Private.KernelFutureFlag.GotIdle)) {
|
220 | this._handleDone();
|
221 | }
|
222 | }
|
223 | async _handleStdin(msg) {
|
224 | this._kernel.hasPendingInput = true;
|
225 | const stdin = this._stdin;
|
226 | if (stdin) {
|
227 | // tslint:disable-next-line:await-promise
|
228 | await stdin(msg);
|
229 | }
|
230 | }
|
231 | async _handleIOPub(msg) {
|
232 | const process = await this._hooks.process(msg);
|
233 | const iopub = this._iopub;
|
234 | if (process && iopub) {
|
235 | // tslint:disable-next-line:await-promise
|
236 | await iopub(msg);
|
237 | }
|
238 | if (KernelMessage.isStatusMsg(msg) &&
|
239 | msg.content.execution_state === 'idle') {
|
240 | this._setFlag(Private.KernelFutureFlag.GotIdle);
|
241 | if (this._testFlag(Private.KernelFutureFlag.GotReply)) {
|
242 | this._handleDone();
|
243 | }
|
244 | }
|
245 | }
|
246 | _handleDone() {
|
247 | if (this._testFlag(Private.KernelFutureFlag.IsDone)) {
|
248 | return;
|
249 | }
|
250 | this._setFlag(Private.KernelFutureFlag.IsDone);
|
251 | this._done.resolve(this._replyMsg);
|
252 | if (this._disposeOnDone) {
|
253 | this.dispose();
|
254 | }
|
255 | }
|
256 | /**
|
257 | * Test whether the given future flag is set.
|
258 | */
|
259 | _testFlag(flag) {
|
260 | // tslint:disable-next-line
|
261 | return (this._status & flag) !== 0;
|
262 | }
|
263 | /**
|
264 | * Set the given future flag.
|
265 | */
|
266 | _setFlag(flag) {
|
267 | // tslint:disable-next-line
|
268 | this._status |= flag;
|
269 | }
|
270 | }
|
271 | exports.KernelFutureHandler = KernelFutureHandler;
|
272 | class KernelControlFutureHandler extends KernelFutureHandler {
|
273 | }
|
274 | exports.KernelControlFutureHandler = KernelControlFutureHandler;
|
275 | class KernelShellFutureHandler extends KernelFutureHandler {
|
276 | }
|
277 | exports.KernelShellFutureHandler = KernelShellFutureHandler;
|
278 | var Private;
|
279 | (function (Private) {
|
280 | /**
|
281 | * A no-op function.
|
282 | */
|
283 | Private.noOp = () => {
|
284 | /* no-op */
|
285 | };
|
286 | /**
|
287 | * Defer a computation.
|
288 | *
|
289 | * #### NOTES
|
290 | * We can't just use requestAnimationFrame since it is not available in node.
|
291 | * This implementation is from Phosphor:
|
292 | * https://github.com/phosphorjs/phosphor/blob/e88e4321289bb1198f3098e7bda40736501f2ed8/tests/test-messaging/src/index.spec.ts#L63
|
293 | */
|
294 | const defer = (() => {
|
295 | const ok = typeof requestAnimationFrame === 'function';
|
296 | return ok ? requestAnimationFrame : setImmediate;
|
297 | })();
|
298 | class HookList {
|
299 | constructor() {
|
300 | this._hooks = [];
|
301 | }
|
302 | /**
|
303 | * Register a hook.
|
304 | *
|
305 | * @param hook - The callback to register.
|
306 | */
|
307 | add(hook) {
|
308 | this.remove(hook);
|
309 | this._hooks.push(hook);
|
310 | }
|
311 | /**
|
312 | * Remove a hook, if it exists in the hook list.
|
313 | *
|
314 | * @param hook - The callback to remove.
|
315 | */
|
316 | remove(hook) {
|
317 | const index = this._hooks.indexOf(hook);
|
318 | if (index >= 0) {
|
319 | this._hooks[index] = null;
|
320 | this._scheduleCompact();
|
321 | }
|
322 | }
|
323 | /**
|
324 | * Process a message through the hooks.
|
325 | *
|
326 | * @returns a promise resolving to false if any hook resolved as false,
|
327 | * otherwise true
|
328 | *
|
329 | * #### Notes
|
330 | * The most recently registered hook is run first. A hook can return a
|
331 | * boolean or a promise to a boolean, in which case processing pauses until
|
332 | * the promise is fulfilled. If a hook return value resolves to false, any
|
333 | * later hooks will not run and the function will return a promise resolving
|
334 | * to false. If a hook throws an error, the error is logged to the console
|
335 | * and the next hook is run. If a hook is registered during the hook
|
336 | * processing, it will not run until the next message. If a hook is removed
|
337 | * during the hook processing, it will be deactivated immediately.
|
338 | */
|
339 | async process(msg) {
|
340 | // Wait until we can start a new process run.
|
341 | await this._processing;
|
342 | // Start the next process run.
|
343 | const processing = new coreutils_1.PromiseDelegate();
|
344 | this._processing = processing.promise;
|
345 | let continueHandling;
|
346 | // Call the end hook (most recently-added) first. Starting at the end also
|
347 | // guarantees that hooks added during the processing will not be run in
|
348 | // this process run.
|
349 | for (let i = this._hooks.length - 1; i >= 0; i--) {
|
350 | const hook = this._hooks[i];
|
351 | // If the hook has been removed, continue to the next one.
|
352 | if (hook === null) {
|
353 | continue;
|
354 | }
|
355 | // Execute the hook and log any errors.
|
356 | try {
|
357 | // tslint:disable-next-line:await-promise
|
358 | continueHandling = await hook(msg);
|
359 | }
|
360 | catch (err) {
|
361 | continueHandling = true;
|
362 | console.error(err);
|
363 | }
|
364 | // If the hook resolved to false, stop processing and return.
|
365 | if (continueHandling === false) {
|
366 | processing.resolve(undefined);
|
367 | return false;
|
368 | }
|
369 | }
|
370 | // All hooks returned true (or errored out), so return true.
|
371 | processing.resolve(undefined);
|
372 | return true;
|
373 | }
|
374 | /**
|
375 | * Schedule a cleanup of the list, removing any hooks that have been nulled out.
|
376 | */
|
377 | _scheduleCompact() {
|
378 | if (!this._compactScheduled) {
|
379 | this._compactScheduled = true;
|
380 | // Schedule a compaction in between processing runs. We do the
|
381 | // scheduling in an animation frame to rate-limit our compactions. If we
|
382 | // need to compact more frequently, we can change this to directly
|
383 | // schedule the compaction.
|
384 | defer(() => {
|
385 | this._processing = this._processing.then(() => {
|
386 | this._compactScheduled = false;
|
387 | this._compact();
|
388 | });
|
389 | });
|
390 | }
|
391 | }
|
392 | /**
|
393 | * Compact the list, removing any nulls.
|
394 | */
|
395 | _compact() {
|
396 | let numNulls = 0;
|
397 | for (let i = 0, len = this._hooks.length; i < len; i++) {
|
398 | const hook = this._hooks[i];
|
399 | if (this._hooks[i] === null) {
|
400 | numNulls++;
|
401 | }
|
402 | else {
|
403 | this._hooks[i - numNulls] = hook;
|
404 | }
|
405 | }
|
406 | this._hooks.length -= numNulls;
|
407 | }
|
408 | }
|
409 | Private.HookList = HookList;
|
410 | /**
|
411 | * Bit flags for the kernel future state.
|
412 | */
|
413 | let KernelFutureFlag;
|
414 | (function (KernelFutureFlag) {
|
415 | KernelFutureFlag[KernelFutureFlag["GotReply"] = 1] = "GotReply";
|
416 | KernelFutureFlag[KernelFutureFlag["GotIdle"] = 2] = "GotIdle";
|
417 | KernelFutureFlag[KernelFutureFlag["IsDone"] = 4] = "IsDone";
|
418 | KernelFutureFlag[KernelFutureFlag["DisposeOnDone"] = 8] = "DisposeOnDone";
|
419 | })(KernelFutureFlag = Private.KernelFutureFlag || (Private.KernelFutureFlag = {}));
|
420 | })(Private || (Private = {}));
|
421 | //# sourceMappingURL=future.js.map |
\ | No newline at end of file |