@jupyterlab/services
Version:
Client APIs for the Jupyter services REST APIs
421 lines • 14.8 kB
JavaScript
"use strict";
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.KernelShellFutureHandler = exports.KernelControlFutureHandler = exports.KernelFutureHandler = void 0;
const coreutils_1 = require("@lumino/coreutils");
const disposable_1 = require("@lumino/disposable");
const KernelMessage = __importStar(require("./messages"));
/**
* Implementation of a kernel future.
*
* If a reply is expected, the Future is considered done when both a `reply`
* message and an `idle` iopub status message have been received. Otherwise, it
* is considered done when the `idle` status is received.
*
*/
class KernelFutureHandler extends disposable_1.DisposableDelegate {
/**
* Construct a new KernelFutureHandler.
*/
constructor(cb, msg, expectReply, disposeOnDone, kernel) {
super(cb);
this._status = 0;
this._stdin = Private.noOp;
this._iopub = Private.noOp;
this._reply = Private.noOp;
this._done = new coreutils_1.PromiseDelegate();
this._hooks = new Private.HookList();
this._disposeOnDone = true;
this._msg = msg;
if (!expectReply) {
this._setFlag(Private.KernelFutureFlag.GotReply);
}
this._disposeOnDone = disposeOnDone;
this._kernel = kernel;
}
/**
* Get the original outgoing message.
*/
get msg() {
return this._msg;
}
/**
* A promise that resolves when the future is done.
*/
get done() {
return this._done.promise;
}
/**
* Get the reply handler.
*/
get onReply() {
return this._reply;
}
/**
* Set the reply handler.
*/
set onReply(cb) {
this._reply = cb;
}
/**
* Get the iopub handler.
*/
get onIOPub() {
return this._iopub;
}
/**
* Set the iopub handler.
*/
set onIOPub(cb) {
this._iopub = cb;
}
/**
* Get the stdin handler.
*/
get onStdin() {
return this._stdin;
}
/**
* Set the stdin handler.
*/
set onStdin(cb) {
this._stdin = cb;
}
/**
* Register hook for IOPub messages.
*
* @param hook - The callback invoked for an IOPub message.
*
* #### Notes
* The IOPub hook system allows you to preempt the handlers for IOPub
* messages handled by the future.
*
* The most recently registered hook is run first. A hook can return a
* boolean or a promise to a boolean, in which case all kernel message
* processing pauses until the promise is fulfilled. If a hook return value
* resolves to false, any later hooks will not run and the function will
* return a promise resolving to false. If a hook throws an error, the error
* is logged to the console and the next hook is run. If a hook is
* registered during the hook processing, it will not run until the next
* message. If a hook is removed during the hook processing, it will be
* deactivated immediately.
*/
registerMessageHook(hook) {
if (this.isDisposed) {
throw new Error('Kernel future is disposed');
}
this._hooks.add(hook);
}
/**
* Remove a hook for IOPub messages.
*
* @param hook - The hook to remove.
*
* #### Notes
* If a hook is removed during the hook processing, it will be deactivated immediately.
*/
removeMessageHook(hook) {
if (this.isDisposed) {
return;
}
this._hooks.remove(hook);
}
/**
* Send an `input_reply` message.
*/
sendInputReply(content, parent_header) {
this._kernel.sendInputReply(content, parent_header);
}
/**
* Dispose and unregister the future.
*/
dispose() {
this._stdin = Private.noOp;
this._iopub = Private.noOp;
this._reply = Private.noOp;
this._hooks = null;
if (!this._testFlag(Private.KernelFutureFlag.IsDone)) {
// TODO: Uncomment the following logging code, and check for any tests that trigger it.
// let status = [];
// if (!this._testFlag(Private.KernelFutureFlag.GotIdle)) {
// status.push('idle');
// }
// if (!this._testFlag(Private.KernelFutureFlag.GotReply)) {
// status.push('reply');
// }
// console.warn(
// `*************** DISPOSED BEFORE DONE: K${this._kernel.id.slice(
// 0,
// 6
// )} M${this._msg.header.msg_id.slice(0, 6)} missing ${status.join(' ')}`
// );
// Reject the `done` promise, but catch its error here in case no one else
// is waiting for the promise to resolve. This prevents the error from
// being displayed in the console, but does not prevent it from being
// caught by a client who is waiting for it.
// Note: any `.then` and `.finally` attached to the `done` promise
// will cause the error to be thrown as uncaught anyways.
this._done.promise.catch(() => {
/* no-op */
});
this._done.reject(new Error(`Canceled future for ${this.msg.header.msg_type} message before replies were done`));
}
super.dispose();
}
/**
* Handle an incoming kernel message.
*/
async handleMsg(msg) {
switch (msg.channel) {
case 'control':
case 'shell':
if (msg.channel === this.msg.channel &&
msg.parent_header.msg_id === this.msg.header.msg_id) {
await this._handleReply(msg);
}
break;
case 'stdin':
await this._handleStdin(msg);
break;
case 'iopub':
await this._handleIOPub(msg);
break;
default:
break;
}
}
async _handleReply(msg) {
const reply = this._reply;
if (reply) {
// tslint:disable-next-line:await-promise
await reply(msg);
}
this._replyMsg = msg;
this._setFlag(Private.KernelFutureFlag.GotReply);
if (this._testFlag(Private.KernelFutureFlag.GotIdle)) {
this._handleDone();
}
}
async _handleStdin(msg) {
this._kernel.hasPendingInput = true;
const stdin = this._stdin;
if (stdin) {
// tslint:disable-next-line:await-promise
await stdin(msg);
}
}
async _handleIOPub(msg) {
const process = await this._hooks.process(msg);
const iopub = this._iopub;
if (process && iopub) {
// tslint:disable-next-line:await-promise
await iopub(msg);
}
if (KernelMessage.isStatusMsg(msg) &&
msg.content.execution_state === 'idle') {
this._setFlag(Private.KernelFutureFlag.GotIdle);
if (this._testFlag(Private.KernelFutureFlag.GotReply)) {
this._handleDone();
}
}
}
_handleDone() {
if (this._testFlag(Private.KernelFutureFlag.IsDone)) {
return;
}
this._setFlag(Private.KernelFutureFlag.IsDone);
this._done.resolve(this._replyMsg);
if (this._disposeOnDone) {
this.dispose();
}
}
/**
* Test whether the given future flag is set.
*/
_testFlag(flag) {
// tslint:disable-next-line
return (this._status & flag) !== 0;
}
/**
* Set the given future flag.
*/
_setFlag(flag) {
// tslint:disable-next-line
this._status |= flag;
}
}
exports.KernelFutureHandler = KernelFutureHandler;
class KernelControlFutureHandler extends KernelFutureHandler {
}
exports.KernelControlFutureHandler = KernelControlFutureHandler;
class KernelShellFutureHandler extends KernelFutureHandler {
}
exports.KernelShellFutureHandler = KernelShellFutureHandler;
var Private;
(function (Private) {
/**
* A no-op function.
*/
Private.noOp = () => {
/* no-op */
};
/**
* Defer a computation.
*
* #### NOTES
* We can't just use requestAnimationFrame since it is not available in node.
* This implementation is from Phosphor:
* https://github.com/phosphorjs/phosphor/blob/e88e4321289bb1198f3098e7bda40736501f2ed8/tests/test-messaging/src/index.spec.ts#L63
*/
const defer = (() => {
const ok = typeof requestAnimationFrame === 'function';
return ok ? requestAnimationFrame : setImmediate;
})();
class HookList {
constructor() {
this._hooks = [];
}
/**
* Register a hook.
*
* @param hook - The callback to register.
*/
add(hook) {
this.remove(hook);
this._hooks.push(hook);
}
/**
* Remove a hook, if it exists in the hook list.
*
* @param hook - The callback to remove.
*/
remove(hook) {
const index = this._hooks.indexOf(hook);
if (index >= 0) {
this._hooks[index] = null;
this._scheduleCompact();
}
}
/**
* Process a message through the hooks.
*
* @returns a promise resolving to false if any hook resolved as false,
* otherwise true
*
* #### Notes
* The most recently registered hook is run first. A hook can return a
* boolean or a promise to a boolean, in which case processing pauses until
* the promise is fulfilled. If a hook return value resolves to false, any
* later hooks will not run and the function will return a promise resolving
* to false. If a hook throws an error, the error is logged to the console
* and the next hook is run. If a hook is registered during the hook
* processing, it will not run until the next message. If a hook is removed
* during the hook processing, it will be deactivated immediately.
*/
async process(msg) {
// Wait until we can start a new process run.
await this._processing;
// Start the next process run.
const processing = new coreutils_1.PromiseDelegate();
this._processing = processing.promise;
let continueHandling;
// Call the end hook (most recently-added) first. Starting at the end also
// guarantees that hooks added during the processing will not be run in
// this process run.
for (let i = this._hooks.length - 1; i >= 0; i--) {
const hook = this._hooks[i];
// If the hook has been removed, continue to the next one.
if (hook === null) {
continue;
}
// Execute the hook and log any errors.
try {
// tslint:disable-next-line:await-promise
continueHandling = await hook(msg);
}
catch (err) {
continueHandling = true;
console.error(err);
}
// If the hook resolved to false, stop processing and return.
if (continueHandling === false) {
processing.resolve(undefined);
return false;
}
}
// All hooks returned true (or errored out), so return true.
processing.resolve(undefined);
return true;
}
/**
* Schedule a cleanup of the list, removing any hooks that have been nulled out.
*/
_scheduleCompact() {
if (!this._compactScheduled) {
this._compactScheduled = true;
// Schedule a compaction in between processing runs. We do the
// scheduling in an animation frame to rate-limit our compactions. If we
// need to compact more frequently, we can change this to directly
// schedule the compaction.
defer(() => {
this._processing = this._processing.then(() => {
this._compactScheduled = false;
this._compact();
});
});
}
}
/**
* Compact the list, removing any nulls.
*/
_compact() {
let numNulls = 0;
for (let i = 0, len = this._hooks.length; i < len; i++) {
const hook = this._hooks[i];
if (this._hooks[i] === null) {
numNulls++;
}
else {
this._hooks[i - numNulls] = hook;
}
}
this._hooks.length -= numNulls;
}
}
Private.HookList = HookList;
/**
* Bit flags for the kernel future state.
*/
let KernelFutureFlag;
(function (KernelFutureFlag) {
KernelFutureFlag[KernelFutureFlag["GotReply"] = 1] = "GotReply";
KernelFutureFlag[KernelFutureFlag["GotIdle"] = 2] = "GotIdle";
KernelFutureFlag[KernelFutureFlag["IsDone"] = 4] = "IsDone";
KernelFutureFlag[KernelFutureFlag["DisposeOnDone"] = 8] = "DisposeOnDone";
})(KernelFutureFlag = Private.KernelFutureFlag || (Private.KernelFutureFlag = {}));
})(Private || (Private = {}));
//# sourceMappingURL=future.js.map