UNPKG

14.8 kBJavaScriptView Raw
1"use strict";
2// Copyright (c) Jupyter Development Team.
3// Distributed under the terms of the Modified BSD License.
4var __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}));
15var __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});
20var __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};
27Object.defineProperty(exports, "__esModule", { value: true });
28exports.KernelShellFutureHandler = exports.KernelControlFutureHandler = exports.KernelFutureHandler = void 0;
29const coreutils_1 = require("@lumino/coreutils");
30const disposable_1 = require("@lumino/disposable");
31const 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 */
40class 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}
271exports.KernelFutureHandler = KernelFutureHandler;
272class KernelControlFutureHandler extends KernelFutureHandler {
273}
274exports.KernelControlFutureHandler = KernelControlFutureHandler;
275class KernelShellFutureHandler extends KernelFutureHandler {
276}
277exports.KernelShellFutureHandler = KernelShellFutureHandler;
278var 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