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