@tlockney/cdp-kernel 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/browser/channels.d.ts +27 -0
- package/dist/browser/channels.js +90 -0
- package/dist/browser/kernel.js +29674 -0
- package/dist/browser/kernel.js.map +7 -0
- package/dist/browser/mod.d.ts +20 -0
- package/dist/browser/mod.js +38 -0
- package/dist/browser/target.d.ts +71 -0
- package/dist/browser/target.js +206 -0
- package/dist/browser/worker.js +1000 -0
- package/dist/browser/worker.js.map +7 -0
- package/dist/cdp.d.ts +9 -0
- package/dist/debugger.d.ts +2 -0
- package/dist/debugger.js +12 -6
- package/dist/kernel-core.d.ts +69 -0
- package/dist/kernel-core.js +754 -0
- package/dist/kernel.d.ts +6 -1
- package/dist/kernel.js +92 -723
- package/dist/message.d.ts +34 -0
- package/dist/message.js +14 -0
- package/dist/package.d.ts +2 -2
- package/dist/package.js +2 -2
- package/dist/target-api.d.ts +80 -0
- package/dist/target-api.js +22 -0
- package/dist/target.d.ts +4 -76
- package/dist/target.js +39 -22
- package/dist/traceback.d.ts +75 -0
- package/dist/traceback.js +200 -0
- package/dist/transform.d.ts +1 -0
- package/dist/transform.js +8 -1
- package/dist/wire.d.ts +4 -24
- package/dist/wire.js +3 -12
- package/dist/zmq-channels.d.ts +14 -0
- package/dist/zmq-channels.js +59 -0
- package/package.json +7 -2
- package/runtime/browser-worker.mjs +119 -0
- package/runtime/cdn.d.mts +2 -0
- package/runtime/cdn.mjs +17 -0
- package/runtime/display.mjs +202 -1
- package/runtime/format.d.mts +2 -0
- package/runtime/format.mjs +125 -0
package/dist/kernel.js
CHANGED
|
@@ -1,29 +1,46 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
// The thirteen op_jupyter_* calls into Deno's REPL isolate are replaced by
|
|
6
|
-
// a Target driven over CDP (src/target.ts).
|
|
7
|
-
import { Buffer } from "node:buffer";
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
// The kernel process: KernelCore (src/kernel-core.ts) on the connection
|
|
2
|
+
// file's ZeroMQ ports, with a target runtime launched as a child process,
|
|
3
|
+
// the language service in a worker thread, and the visual debugger.
|
|
4
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
9
5
|
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
10
7
|
import process from "node:process";
|
|
11
|
-
import {
|
|
12
|
-
import { decodeMessage, encodeMessage, makeHeader, PROTOCOL_VERSION, verify } from "./wire.js";
|
|
13
|
-
import { listening, PubSocket, RouterSocket, startHeartbeat } from "./zmtp.js";
|
|
14
|
-
import { Interrupted, Target } from "./target.js";
|
|
8
|
+
import { Target } from "./target.js";
|
|
15
9
|
import { BunTarget } from "./bun-target.js";
|
|
16
10
|
import { Debugger } from "./debugger.js";
|
|
17
11
|
import { WorkerdTarget } from "./workerd-target.js";
|
|
18
12
|
import { launchCelld } from "./celld-target.js";
|
|
19
|
-
import { CellSyntaxError, loadTypeStripper, objectLiteralCandidate, } from "./transform.js";
|
|
20
13
|
import { loadDenoProject } from "./deno-config.js";
|
|
21
|
-
import { checkIsComplete, codePointToUtf16Index, exprAtCursor, inspectTarget, utf16ToCodePointIndex } from "./text.js";
|
|
22
|
-
import { queryHistory } from "./history.js";
|
|
23
14
|
import { LanguageService } from "./language-service.js";
|
|
24
15
|
import { findWranglerConfig } from "./workers-project.js";
|
|
25
|
-
import { VERSION } from "./package.js";
|
|
16
|
+
import { cacheRoot, VERSION } from "./package.js";
|
|
26
17
|
import { watchParent } from "./parent.js";
|
|
18
|
+
import { KernelCore } from "./kernel-core.js";
|
|
19
|
+
import { ZmqChannels } from "./zmq-channels.js";
|
|
20
|
+
/** Larger files get no source lines in a traceback. */
|
|
21
|
+
const MAX_SOURCE_BYTES = 1 << 20;
|
|
22
|
+
/** A traceback frame's file, read from disk when it is a local one (#322). */
|
|
23
|
+
function readSource(file) {
|
|
24
|
+
try {
|
|
25
|
+
const path = file.startsWith("file:") ? fileURLToPath(file) : file;
|
|
26
|
+
if (!path.startsWith("/") && !/^[A-Za-z]:[\\/]/.test(path))
|
|
27
|
+
return undefined;
|
|
28
|
+
if (statSync(path).size > MAX_SOURCE_BYTES)
|
|
29
|
+
return undefined;
|
|
30
|
+
return readFileSync(path, "utf8");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Where the target loads the kernel's runtime/*.mjs from: in place, or a cached copy (src/package.ts). */
|
|
37
|
+
function kernelFiles() {
|
|
38
|
+
const runtime = new URL("../runtime/", import.meta.url);
|
|
39
|
+
const files = [runtime.href, join(cacheRoot(), "runtime")];
|
|
40
|
+
if (runtime.protocol === "file:")
|
|
41
|
+
files.push(fileURLToPath(runtime));
|
|
42
|
+
return files;
|
|
43
|
+
}
|
|
27
44
|
export function parseConnectionInfo(json) {
|
|
28
45
|
const raw = JSON.parse(json);
|
|
29
46
|
if (typeof raw !== "object" || raw === null)
|
|
@@ -55,21 +72,9 @@ export function parseConnectionInfo(json) {
|
|
|
55
72
|
hb_port: info.hb_port,
|
|
56
73
|
};
|
|
57
74
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
*/
|
|
62
|
-
const INTERRUPT_REPLY_WAIT_MS = 1_000;
|
|
63
|
-
const NO_INPUT = { value: null, interrupted: false };
|
|
64
|
-
/** Shell requests other than execute_request, and their reply types. */
|
|
65
|
-
const SHELL_REPLIES = {
|
|
66
|
-
kernel_info_request: "kernel_info_reply",
|
|
67
|
-
complete_request: "complete_reply",
|
|
68
|
-
is_complete_request: "is_complete_reply",
|
|
69
|
-
inspect_request: "inspect_reply",
|
|
70
|
-
history_request: "history_reply",
|
|
71
|
-
comm_info_request: "comm_info_reply",
|
|
72
|
-
};
|
|
75
|
+
function log(text) {
|
|
76
|
+
process.stderr.write(`[cdp-kernel] ${text}\n`);
|
|
77
|
+
}
|
|
73
78
|
/** The target Node's major version, for @types/node: the host's, which is usually the same binary. */
|
|
74
79
|
function nodeMajor() {
|
|
75
80
|
return process.versions.node?.split(".")[0] ?? "*";
|
|
@@ -100,715 +105,79 @@ function languageServiceOptions(runtime, notebookDir, project) {
|
|
|
100
105
|
}
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
function jupyterType(kind) {
|
|
109
|
-
switch (kind) {
|
|
110
|
-
case undefined:
|
|
111
|
-
return "<unknown>";
|
|
112
|
-
case "var":
|
|
113
|
-
case "let":
|
|
114
|
-
case "const":
|
|
115
|
-
case "local var":
|
|
116
|
-
case "parameter":
|
|
117
|
-
return "variable";
|
|
118
|
-
case "local function":
|
|
119
|
-
return "function";
|
|
120
|
-
case "type":
|
|
121
|
-
case "alias":
|
|
122
|
-
return "type";
|
|
123
|
-
default:
|
|
124
|
-
return kind;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
108
|
+
/**
|
|
109
|
+
* A kernel on its connection file's ports, running cells on a target
|
|
110
|
+
* runtime launched as a child process. The message handling is
|
|
111
|
+
* KernelCore's (src/kernel-core.ts); this is its Node wiring.
|
|
112
|
+
*/
|
|
127
113
|
export class Kernel {
|
|
128
|
-
#
|
|
129
|
-
#runtime;
|
|
130
|
-
#denoCompat;
|
|
131
|
-
#typeStrip;
|
|
132
|
-
// A Deno target in a Deno project uses its config (#13).
|
|
133
|
-
#project;
|
|
134
|
-
#projectWarning = null;
|
|
135
|
-
#noticeShownFor = null;
|
|
136
|
-
/** JupyterLab's visual debugger, for targets driven over CDP (#18). */
|
|
137
|
-
#debugger;
|
|
138
|
-
/** Typed completion from TypeScript's language service (#273). */
|
|
139
|
-
#types;
|
|
140
|
-
/** Cells run with store_history, for history_request (#10). */
|
|
141
|
-
#history = [];
|
|
142
|
-
#session = randomUUID();
|
|
143
|
-
#shell;
|
|
144
|
-
#control;
|
|
145
|
-
#stdin;
|
|
146
|
-
#iopub;
|
|
147
|
-
/** Settles once all five ports are bound (#257). */
|
|
148
|
-
#bound;
|
|
149
|
-
#target = null;
|
|
150
|
-
#targetStarting = null;
|
|
151
|
-
#executionCount = 0;
|
|
152
|
-
#parent = {};
|
|
153
|
-
#allowStdin = false;
|
|
154
|
-
#executing = false;
|
|
155
|
-
/**
|
|
156
|
-
* stop_on_error (#123): how many of the shell messages queued when a cell
|
|
157
|
-
* failed are still to come, and whether the one being handled is one.
|
|
158
|
-
*/
|
|
159
|
-
#abortQueued = 0;
|
|
160
|
-
#aborting = false;
|
|
161
|
-
/** The input_request waiting for its input_reply, if any. */
|
|
162
|
-
#input = null;
|
|
163
|
-
/** An interrupt is waiting on the target. */
|
|
164
|
-
#interrupting = false;
|
|
165
|
-
/** The shell message being handled, and since when. */
|
|
166
|
-
#handling = null;
|
|
167
|
-
// Open comms, for comm_info_request: comm_id -> target_name (#9).
|
|
168
|
-
#comms = new Map();
|
|
169
|
-
#shuttingDown = false;
|
|
114
|
+
#core;
|
|
170
115
|
constructor(conn, runtime, options = {}) {
|
|
171
|
-
this.#conn = conn;
|
|
172
|
-
this.#runtime = runtime;
|
|
173
|
-
this.#denoCompat = options.denoCompat ?? false;
|
|
174
|
-
this.#typeStrip = options.typeStrip ?? "esbuild";
|
|
175
116
|
const notebookDir = options.notebookDir ?? process.cwd();
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
117
|
+
let warning = null;
|
|
118
|
+
let project = null;
|
|
119
|
+
// A Deno target in a Deno project uses its config (#13).
|
|
120
|
+
if (runtime === "deno") {
|
|
121
|
+
try {
|
|
122
|
+
project = loadDenoProject(notebookDir);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
// A broken deno.json mustn't stop the kernel: run as in a plain
|
|
126
|
+
// directory and say why in the first cell.
|
|
127
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
128
|
+
warning = `${reason}; running without the project config`;
|
|
129
|
+
log(warning);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const denoCompat = options.denoCompat ?? false;
|
|
133
|
+
this.#core = new KernelCore({
|
|
134
|
+
runtime,
|
|
135
|
+
version: VERSION,
|
|
136
|
+
channels: new ZmqChannels(conn, log),
|
|
137
|
+
launchTarget(events) {
|
|
138
|
+
if (runtime === "bun")
|
|
139
|
+
return BunTarget.launch(events);
|
|
140
|
+
if (runtime === "workerd")
|
|
141
|
+
return WorkerdTarget.launch(events);
|
|
142
|
+
if (runtime === "celld")
|
|
143
|
+
return launchCelld(events);
|
|
144
|
+
return Target.launch(runtime, events, { denoCompat, denoConfig: project?.configPath });
|
|
145
|
+
},
|
|
146
|
+
typeStrip: options.typeStrip,
|
|
147
|
+
transform: project?.transform,
|
|
148
|
+
warning,
|
|
149
|
+
types: options.languageService === false
|
|
150
|
+
? null
|
|
151
|
+
: new LanguageService(languageServiceOptions(runtime, notebookDir, project), { log }),
|
|
152
|
+
createDebugger: runtime === "node" || runtime === "deno"
|
|
153
|
+
? (publish, transformOptions) => new Debugger(publish, transformOptions)
|
|
154
|
+
: undefined,
|
|
155
|
+
readSource,
|
|
156
|
+
kernelFiles: kernelFiles(),
|
|
157
|
+
log,
|
|
158
|
+
exit: () => process.exit(0),
|
|
159
|
+
});
|
|
189
160
|
}
|
|
190
161
|
async run() {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
process.on("SIGINT", () => void this.#interrupt());
|
|
162
|
+
const core = this.#core;
|
|
163
|
+
process.on("SIGINT", () => void core.interrupt());
|
|
194
164
|
// Exiting runs the exit hook that kills the runtime (src/children.ts);
|
|
195
165
|
// these signals' default action would skip it (#218).
|
|
196
166
|
for (const [signal, code] of [["SIGTERM", 143], ["SIGHUP", 129]]) {
|
|
197
167
|
process.on(signal, () => {
|
|
198
|
-
|
|
168
|
+
core.stopping();
|
|
199
169
|
process.exit(code);
|
|
200
170
|
});
|
|
201
171
|
}
|
|
202
172
|
// A server killed before it could shut the kernel down (#254).
|
|
203
173
|
watchParent(() => {
|
|
204
|
-
|
|
205
|
-
|
|
174
|
+
log(`parent ${process.env.JPY_PARENT_PID} exited; shutting down`);
|
|
175
|
+
core.stopping();
|
|
206
176
|
process.exit(1);
|
|
207
177
|
});
|
|
208
178
|
// A state dump on stderr, for a kernel that has stopped answering (#237).
|
|
209
179
|
if (process.platform !== "win32")
|
|
210
|
-
process.on("SIGUSR2", () => void
|
|
211
|
-
|
|
212
|
-
if (typeof this.#typeStrip === "string")
|
|
213
|
-
await loadTypeStripper(this.#typeStrip);
|
|
214
|
-
await Promise.all([this.#loop(this.#shell, (m, p) => this.#handleShell(m, p)),
|
|
215
|
-
this.#loop(this.#control, (m, p) => this.#handleControl(m, p)),
|
|
216
|
-
this.#loop(this.#stdin, (m) => this.#handleStdin(m))]);
|
|
217
|
-
}
|
|
218
|
-
#log(text) {
|
|
219
|
-
process.stderr.write(`[cdp-kernel] ${text}\n`);
|
|
220
|
-
}
|
|
221
|
-
async #logState() {
|
|
222
|
-
const handling = this.#handling;
|
|
223
|
-
const target = this.#target;
|
|
224
|
-
let targetState = target === null ? (this.#targetStarting === null ? null : "starting") : target.runtime;
|
|
225
|
-
if (target?.diagnose)
|
|
226
|
-
targetState = await target.diagnose().catch((err) => `diagnose failed: ${err}`);
|
|
227
|
-
this.#log(`state: ${JSON.stringify({
|
|
228
|
-
shell: handling && { msgType: handling.msgType, msgId: handling.msgId, ms: Date.now() - handling.since },
|
|
229
|
-
queued: this.#shell.incoming.size,
|
|
230
|
-
executing: this.#executing,
|
|
231
|
-
interrupting: this.#interrupting,
|
|
232
|
-
awaitingInput: this.#input !== null,
|
|
233
|
-
target: targetState,
|
|
234
|
-
})}`);
|
|
235
|
-
}
|
|
236
|
-
#loadProject(notebookDir) {
|
|
237
|
-
try {
|
|
238
|
-
return loadDenoProject(notebookDir);
|
|
239
|
-
}
|
|
240
|
-
catch (err) {
|
|
241
|
-
// A broken deno.json mustn't stop the kernel: run as in a plain
|
|
242
|
-
// directory and say why in the first cell.
|
|
243
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
244
|
-
this.#projectWarning = `${reason}; running without the project config`;
|
|
245
|
-
this.#log(this.#projectWarning);
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
#transformOptions() {
|
|
250
|
-
return { ...this.#project?.transform, typeStrip: this.#typeStrip };
|
|
251
|
-
}
|
|
252
|
-
// --- comms (#9) ------------------------------------------------------------
|
|
253
|
-
#trackComm(msgType, content) {
|
|
254
|
-
const commId = typeof content.comm_id === "string" ? content.comm_id : null;
|
|
255
|
-
if (commId === null)
|
|
256
|
-
return;
|
|
257
|
-
if (msgType === "comm_open" && typeof content.target_name === "string")
|
|
258
|
-
this.#comms.set(commId, content.target_name);
|
|
259
|
-
else if (msgType === "comm_close")
|
|
260
|
-
this.#comms.delete(commId);
|
|
261
|
-
}
|
|
262
|
-
#commInfo(content) {
|
|
263
|
-
const filter = typeof content.target_name === "string" ? content.target_name : null;
|
|
264
|
-
const comms = {};
|
|
265
|
-
for (const [commId, targetName] of this.#comms) {
|
|
266
|
-
if (filter === null || filter === targetName)
|
|
267
|
-
comms[commId] = { target_name: targetName };
|
|
268
|
-
}
|
|
269
|
-
return comms;
|
|
270
|
-
}
|
|
271
|
-
/** A frontend comm message. The busy/idle status around it is the caller's. */
|
|
272
|
-
async #handleComm(msg) {
|
|
273
|
-
const type = msg.header.msg_type;
|
|
274
|
-
const commId = String(msg.content.comm_id ?? "");
|
|
275
|
-
const target = await this.#ensureTarget();
|
|
276
|
-
// Kernel messages the handlers send are replies to this message.
|
|
277
|
-
this.#parent = msg.header;
|
|
278
|
-
this.#allowStdin = false;
|
|
279
|
-
this.#executing = true;
|
|
280
|
-
let handled = false;
|
|
281
|
-
try {
|
|
282
|
-
const result = await target.dispatchComm(type, msg.content, msg.metadata, msg.buffers);
|
|
283
|
-
if ("error" in result) {
|
|
284
|
-
const { name, message, stack } = result.error;
|
|
285
|
-
this.#publish("stream", { name: "stderr", text: `Error in ${type} handler: ${stack || `${name}: ${message}`}\n` });
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
handled = result.handled;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
catch (err) {
|
|
292
|
-
if (!(err instanceof Interrupted))
|
|
293
|
-
throw err;
|
|
294
|
-
this.#publish("stream", { name: "stderr", text: `${type} handler interrupted\n` });
|
|
295
|
-
}
|
|
296
|
-
finally {
|
|
297
|
-
this.#executing = false;
|
|
298
|
-
}
|
|
299
|
-
await target.flush().catch(() => { });
|
|
300
|
-
if (type === "comm_open") {
|
|
301
|
-
if (handled) {
|
|
302
|
-
this.#comms.set(commId, String(msg.content.target_name ?? ""));
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
// An unknown target, or a handler that failed: the protocol asks the
|
|
306
|
-
// kernel to close the comm so the frontend doesn't wait on it.
|
|
307
|
-
this.#publish("comm_close", { comm_id: commId, data: {} });
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
else if (type === "comm_close") {
|
|
311
|
-
this.#comms.delete(commId);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
// --- target lifecycle -----------------------------------------------------
|
|
315
|
-
#launchTarget() {
|
|
316
|
-
if (this.#runtime === "bun")
|
|
317
|
-
return BunTarget.launch(this.#targetEvents());
|
|
318
|
-
if (this.#runtime === "workerd")
|
|
319
|
-
return WorkerdTarget.launch(this.#targetEvents());
|
|
320
|
-
if (this.#runtime === "celld")
|
|
321
|
-
return launchCelld(this.#targetEvents());
|
|
322
|
-
return Target.launch(this.#runtime, this.#targetEvents(), {
|
|
323
|
-
denoCompat: this.#denoCompat,
|
|
324
|
-
denoConfig: this.#project?.configPath,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
#ensureTarget() {
|
|
328
|
-
if (this.#target)
|
|
329
|
-
return Promise.resolve(this.#target);
|
|
330
|
-
// Not before the kernel's own ports are bound: a Deno runtime binds its
|
|
331
|
-
// inspector to port 0 within milliseconds of starting and can take a
|
|
332
|
-
// connection-file port the kernel is still binding (#257).
|
|
333
|
-
this.#targetStarting ??= this.#bound.then(() => this.#launchTarget()).then(async (target) => {
|
|
334
|
-
// Before any cell runs on it, so breakpoints already apply.
|
|
335
|
-
await this.#debugger?.attachTarget(target.debug ?? null);
|
|
336
|
-
this.#target = target;
|
|
337
|
-
this.#targetStarting = null;
|
|
338
|
-
return target;
|
|
339
|
-
}, (err) => {
|
|
340
|
-
this.#targetStarting = null;
|
|
341
|
-
throw err;
|
|
342
|
-
});
|
|
343
|
-
return this.#targetStarting;
|
|
344
|
-
}
|
|
345
|
-
#targetEvents() {
|
|
346
|
-
return {
|
|
347
|
-
stream: (name, text) => this.#publish("stream", { name, text }),
|
|
348
|
-
message: (msg) => void this.#onTargetMessage(msg).catch((err) => this.#log(`error handling target message: ${err}`)),
|
|
349
|
-
exit: (code, note) => {
|
|
350
|
-
this.#target = null;
|
|
351
|
-
// What its cells declared died with it.
|
|
352
|
-
this.#types?.reset();
|
|
353
|
-
// Its prompt, if any, died with it; a late reply is dropped.
|
|
354
|
-
this.#input = null;
|
|
355
|
-
void this.#debugger?.attachTarget(null);
|
|
356
|
-
if (!this.#shuttingDown) {
|
|
357
|
-
const text = note ?? `[cdp-kernel] ${this.#runtime} exited (${code}); restarting on next cell`;
|
|
358
|
-
this.#publish("stream", { name: "stderr", text: `\n${text}\n` });
|
|
359
|
-
}
|
|
360
|
-
},
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
async #onTargetMessage(msg) {
|
|
364
|
-
if (msg.kind === "iopub") {
|
|
365
|
-
this.#trackComm(msg.msg_type, msg.content);
|
|
366
|
-
this.#publish(msg.msg_type, msg.content, {
|
|
367
|
-
metadata: msg.metadata,
|
|
368
|
-
buffers: msg.buffers.map((b) => new Uint8Array(Buffer.from(b, "base64"))),
|
|
369
|
-
});
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
// The answer goes to the target that asked, not one started since.
|
|
373
|
-
const target = this.#target;
|
|
374
|
-
let answer = NO_INPUT;
|
|
375
|
-
try {
|
|
376
|
-
answer = await this.#requestInput(msg.prompt, msg.password);
|
|
377
|
-
}
|
|
378
|
-
finally {
|
|
379
|
-
// Always answer exactly once so the target's blocking read returns.
|
|
380
|
-
if (target !== null && target === this.#target)
|
|
381
|
-
target.replyInput(answer.value, answer.interrupted);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
async #interrupt() {
|
|
385
|
-
// A target blocked reading its prompt's answer can't run
|
|
386
|
-
// terminateExecution, and would never answer it. Make the prompt throw
|
|
387
|
-
// instead, which ends the cell (#121).
|
|
388
|
-
const input = this.#input;
|
|
389
|
-
if (input !== null) {
|
|
390
|
-
this.#input = null;
|
|
391
|
-
input.settle({ value: null, interrupted: true });
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (!this.#target || !this.#executing)
|
|
395
|
-
return;
|
|
396
|
-
this.#interrupting = true;
|
|
397
|
-
try {
|
|
398
|
-
await this.#target.interrupt();
|
|
399
|
-
}
|
|
400
|
-
finally {
|
|
401
|
-
this.#interrupting = false;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// --- messaging --------------------------------------------------------------
|
|
405
|
-
#encode(msgType, content, parentHeader, extra = {}) {
|
|
406
|
-
return encodeMessage(this.#session, this.#conn.key, msgType, content, { parentHeader, ...extra });
|
|
407
|
-
}
|
|
408
|
-
#publish(msgType, content, extra = {}) {
|
|
409
|
-
this.#iopub.send(this.#encode(msgType, content, extra.parent ?? this.#parent, extra));
|
|
410
|
-
}
|
|
411
|
-
#status(state, parent) {
|
|
412
|
-
this.#publish("status", { execution_state: state }, { parent });
|
|
413
|
-
}
|
|
414
|
-
#reply(socket, peer, request, msgType, content) {
|
|
415
|
-
socket.send(peer, this.#encode(msgType, content, request.header));
|
|
416
|
-
}
|
|
417
|
-
async #loop(socket, handle) {
|
|
418
|
-
while (!this.#shuttingDown) {
|
|
419
|
-
const { peer, frames } = await socket.recv();
|
|
420
|
-
if (socket === this.#shell) {
|
|
421
|
-
this.#aborting = this.#abortQueued > 0;
|
|
422
|
-
if (this.#aborting)
|
|
423
|
-
this.#abortQueued--;
|
|
424
|
-
}
|
|
425
|
-
try {
|
|
426
|
-
const msg = decodeMessage(frames);
|
|
427
|
-
// Unsigned or forged messages are dropped, as in Deno.
|
|
428
|
-
if (!verify(this.#conn.key, msg.signedParts, msg.sig))
|
|
429
|
-
continue;
|
|
430
|
-
if (socket === this.#shell)
|
|
431
|
-
this.#handling = { msgType: msg.header.msg_type, msgId: msg.header.msg_id, since: Date.now() };
|
|
432
|
-
await handle(msg, peer);
|
|
433
|
-
}
|
|
434
|
-
catch (err) {
|
|
435
|
-
this.#log(`error handling message: ${err instanceof Error ? err.stack : err}`);
|
|
436
|
-
}
|
|
437
|
-
finally {
|
|
438
|
-
if (socket === this.#shell)
|
|
439
|
-
this.#handling = null;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
#kernelInfo() {
|
|
444
|
-
const versions = this.#target?.versions;
|
|
445
|
-
return {
|
|
446
|
-
status: "ok",
|
|
447
|
-
protocol_version: PROTOCOL_VERSION,
|
|
448
|
-
implementation: "cdp-kernel",
|
|
449
|
-
implementation_version: VERSION,
|
|
450
|
-
language_info: {
|
|
451
|
-
name: "typescript",
|
|
452
|
-
version: "5",
|
|
453
|
-
mimetype: "text/x.typescript",
|
|
454
|
-
file_extension: ".ts",
|
|
455
|
-
pygments_lexer: "typescript",
|
|
456
|
-
codemirror_mode: "typescript",
|
|
457
|
-
nbconvert_exporter: "script",
|
|
458
|
-
},
|
|
459
|
-
// The runtime's versions are known once it is up.
|
|
460
|
-
banner: versions ? `cdp-kernel on ${this.#runtime} ${versions.runtime} (${versions.engine || "?"})` : `cdp-kernel on ${this.#runtime}`,
|
|
461
|
-
help_links: [],
|
|
462
|
-
debugger: this.#debugger !== null,
|
|
463
|
-
// What current ipykernel advertises; JupyterLab itself goes by the
|
|
464
|
-
// kernelspec's metadata.debugger.
|
|
465
|
-
supported_features: this.#debugger !== null ? ["debugger"] : [],
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
// --- control channel ------------------------------------------------------
|
|
469
|
-
async #handleControl(msg, peer) {
|
|
470
|
-
// Every request is wrapped in busy/idle, as on shell (#124). JupyterLab
|
|
471
|
-
// treats a debug request as done only once the kernel reports idle for
|
|
472
|
-
// it. (Jupyter Server leaves debug_request out of the kernel's status.)
|
|
473
|
-
this.#status("busy", msg.header);
|
|
474
|
-
try {
|
|
475
|
-
await this.#controlRequest(this.#control, msg, peer);
|
|
476
|
-
}
|
|
477
|
-
finally {
|
|
478
|
-
this.#status("idle", msg.header);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
/** Control requests; shutdown and interrupt may also arrive on shell (#124). */
|
|
482
|
-
async #controlRequest(socket, msg, peer) {
|
|
483
|
-
switch (msg.header.msg_type) {
|
|
484
|
-
case "kernel_info_request":
|
|
485
|
-
this.#reply(socket, peer, msg, "kernel_info_reply", this.#kernelInfo());
|
|
486
|
-
break;
|
|
487
|
-
case "interrupt_request":
|
|
488
|
-
// Answer even if the target doesn't (#121); the interrupt goes on.
|
|
489
|
-
await Promise.race([
|
|
490
|
-
this.#interrupt().catch((err) => this.#log(`interrupt failed: ${err}`)),
|
|
491
|
-
new Promise((r) => setTimeout(r, INTERRUPT_REPLY_WAIT_MS)),
|
|
492
|
-
]);
|
|
493
|
-
this.#reply(socket, peer, msg, "interrupt_reply", { status: "ok" });
|
|
494
|
-
break;
|
|
495
|
-
case "debug_request": {
|
|
496
|
-
let reply;
|
|
497
|
-
if (this.#debugger === null) {
|
|
498
|
-
reply = { type: "response", request_seq: msg.content.seq, command: msg.content.command, success: false, message: `${this.#runtime} targets have no debugger`, body: {} };
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
// Everything but debugInfo talks to the target, so make sure there is one.
|
|
502
|
-
if (msg.content.command !== "debugInfo")
|
|
503
|
-
await this.#ensureTarget().catch(() => null);
|
|
504
|
-
reply = await this.#debugger.handle(msg.content);
|
|
505
|
-
}
|
|
506
|
-
this.#reply(socket, peer, msg, "debug_reply", reply);
|
|
507
|
-
break;
|
|
508
|
-
}
|
|
509
|
-
case "shutdown_request": {
|
|
510
|
-
const restart = msg.content.restart === true;
|
|
511
|
-
this.#reply(socket, peer, msg, "shutdown_reply", { status: "ok", restart });
|
|
512
|
-
this.#shuttingDown = true;
|
|
513
|
-
this.#target?.kill();
|
|
514
|
-
// Frontends spawn a fresh kernel even on restart; exit after the
|
|
515
|
-
// reply has had a moment to flush (denoland/deno#20556).
|
|
516
|
-
setTimeout(() => process.exit(0), 100);
|
|
517
|
-
break;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
// --- shell channel ----------------------------------------------------------
|
|
522
|
-
async #handleShell(msg, peer) {
|
|
523
|
-
const type = msg.header.msg_type;
|
|
524
|
-
if (type === "execute_request")
|
|
525
|
-
return this.#execute(msg, peer);
|
|
526
|
-
this.#status("busy", msg.header);
|
|
527
|
-
try {
|
|
528
|
-
switch (type) {
|
|
529
|
-
case "kernel_info_request":
|
|
530
|
-
// Without waiting for the runtime: clients probe with kernel_info
|
|
531
|
-
// while it starts, and re-send until answered (#253).
|
|
532
|
-
this.#reply(this.#shell, peer, msg, "kernel_info_reply", this.#kernelInfo());
|
|
533
|
-
break;
|
|
534
|
-
case "complete_request":
|
|
535
|
-
this.#reply(this.#shell, peer, msg, "complete_reply", await this.#complete(msg));
|
|
536
|
-
break;
|
|
537
|
-
case "is_complete_request":
|
|
538
|
-
this.#reply(this.#shell, peer, msg, "is_complete_reply", checkIsComplete(String(msg.content.code ?? "")));
|
|
539
|
-
break;
|
|
540
|
-
case "inspect_request":
|
|
541
|
-
this.#reply(this.#shell, peer, msg, "inspect_reply", await this.#inspect(msg));
|
|
542
|
-
break;
|
|
543
|
-
case "history_request":
|
|
544
|
-
this.#reply(this.#shell, peer, msg, "history_reply", { status: "ok", history: queryHistory(this.#history, msg.content) });
|
|
545
|
-
break;
|
|
546
|
-
case "comm_info_request":
|
|
547
|
-
this.#reply(this.#shell, peer, msg, "comm_info_reply", { status: "ok", comms: this.#commInfo(msg.content) });
|
|
548
|
-
break;
|
|
549
|
-
case "comm_open":
|
|
550
|
-
case "comm_msg":
|
|
551
|
-
case "comm_close":
|
|
552
|
-
await this.#handleComm(msg);
|
|
553
|
-
break;
|
|
554
|
-
case "shutdown_request":
|
|
555
|
-
case "interrupt_request":
|
|
556
|
-
// Deprecated on shell, but still allowed, and ipykernel takes them.
|
|
557
|
-
await this.#controlRequest(this.#shell, msg, peer);
|
|
558
|
-
break;
|
|
559
|
-
default:
|
|
560
|
-
this.#log(`ignoring unknown shell message ${type}`);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
catch (err) {
|
|
564
|
-
// E.g. the runtime failed to launch. Answer anyway, so the frontend
|
|
565
|
-
// doesn't wait on the request forever (#125).
|
|
566
|
-
const replyType = SHELL_REPLIES[type];
|
|
567
|
-
if (replyType !== undefined) {
|
|
568
|
-
const name = err instanceof Error ? err.name : "Error";
|
|
569
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
570
|
-
const error = { ename: name, evalue: message, traceback: [`${name}: ${message}`] };
|
|
571
|
-
this.#reply(this.#shell, peer, msg, replyType, { status: "error", ...error });
|
|
572
|
-
}
|
|
573
|
-
throw err;
|
|
574
|
-
}
|
|
575
|
-
finally {
|
|
576
|
-
this.#status("idle", msg.header);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* The tooltip (Shift-Tab) for the name at the cursor (#10): its declared
|
|
581
|
-
* type, signature and JSDoc when the language service knows them (#274),
|
|
582
|
-
* then what the runtime finds.
|
|
583
|
-
*/
|
|
584
|
-
async #inspect(msg) {
|
|
585
|
-
const code = String(msg.content.code ?? "");
|
|
586
|
-
const cursorCp = typeof msg.content.cursor_pos === "number"
|
|
587
|
-
? msg.content.cursor_pos
|
|
588
|
-
: utf16ToCodePointIndex(code, code.length);
|
|
589
|
-
const cursor = codePointToUtf16Index(code, cursorCp);
|
|
590
|
-
const path = inspectTarget(code, cursor);
|
|
591
|
-
const detail = msg.content.detail_level === 1 ? 1 : 0;
|
|
592
|
-
const [runtime, typed] = await Promise.all([
|
|
593
|
-
path === null ? null : (async () => (await this.#ensureTarget()).inspect(path, detail))(),
|
|
594
|
-
this.#types?.quickInfo(code, cursor) ?? null,
|
|
595
|
-
]);
|
|
596
|
-
const text = typed === null ? runtime : runtime === null ? typed : `${typed}\n\n${withoutSignature(runtime)}`;
|
|
597
|
-
return text === null
|
|
598
|
-
? { status: "ok", found: false, data: {}, metadata: {} }
|
|
599
|
-
: { status: "ok", found: true, data: { "text/plain": text }, metadata: {} };
|
|
600
|
-
}
|
|
601
|
-
async #complete(msg) {
|
|
602
|
-
const code = String(msg.content.code ?? "");
|
|
603
|
-
const cursorCp = typeof msg.content.cursor_pos === "number"
|
|
604
|
-
? msg.content.cursor_pos
|
|
605
|
-
: utf16ToCodePointIndex(code, code.length);
|
|
606
|
-
const cursor = codePointToUtf16Index(code, cursorCp);
|
|
607
|
-
// Both at once: the language service may not answer within its budget.
|
|
608
|
-
const [runtime, typed] = await Promise.all([
|
|
609
|
-
this.#runtimeCompletions(code, cursor),
|
|
610
|
-
this.#types?.complete(code, cursor) ?? null,
|
|
611
|
-
]);
|
|
612
|
-
// Declared types first, then what only the runtime knows (a property
|
|
613
|
-
// added at runtime, a global defined by a library).
|
|
614
|
-
let matches = runtime.matches;
|
|
615
|
-
let start = runtime.start;
|
|
616
|
-
const kinds = new Map();
|
|
617
|
-
if (typed !== null && typed.start === runtime.start) {
|
|
618
|
-
for (const entry of typed.entries)
|
|
619
|
-
kinds.set(entry.name, entry.kind);
|
|
620
|
-
matches = [...kinds.keys(), ...runtime.matches.filter((name) => !kinds.has(name))];
|
|
621
|
-
}
|
|
622
|
-
else if (typed !== null && typed.entries.length > 0 && runtime.matches.length === 0) {
|
|
623
|
-
// An expression the runtime can't complete, e.g. inside a call.
|
|
624
|
-
for (const entry of typed.entries)
|
|
625
|
-
kinds.set(entry.name, entry.kind);
|
|
626
|
-
matches = [...kinds.keys()];
|
|
627
|
-
start = typed.start;
|
|
628
|
-
}
|
|
629
|
-
const cursorStart = utf16ToCodePointIndex(code, start);
|
|
630
|
-
return {
|
|
631
|
-
status: "ok",
|
|
632
|
-
matches,
|
|
633
|
-
cursor_start: cursorStart,
|
|
634
|
-
cursor_end: cursorCp,
|
|
635
|
-
metadata: kinds.size === 0 ? {} : {
|
|
636
|
-
// JupyterLab shows each match's type as an icon.
|
|
637
|
-
_jupyter_types_experimental: matches.map((text) => ({
|
|
638
|
-
start: cursorStart,
|
|
639
|
-
end: cursorCp,
|
|
640
|
-
text,
|
|
641
|
-
type: jupyterType(kinds.get(text)),
|
|
642
|
-
})),
|
|
643
|
-
},
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
/** Completion from the runtime's values: property names, globals, lexical names. */
|
|
647
|
-
async #runtimeCompletions(code, cursor) {
|
|
648
|
-
const expr = exprAtCursor(code, cursor);
|
|
649
|
-
const target = await this.#ensureTarget();
|
|
650
|
-
const dot = expr.lastIndexOf(".");
|
|
651
|
-
if (dot !== -1) {
|
|
652
|
-
const prefix = expr.slice(dot + 1);
|
|
653
|
-
const matches = (await target.propertyNames(expr.slice(0, dot)))
|
|
654
|
-
.filter((name) => name.startsWith(prefix) && !name.startsWith("Symbol("));
|
|
655
|
-
return { matches, start: cursor - prefix.length };
|
|
656
|
-
}
|
|
657
|
-
const names = new Set([...(await target.propertyNames("globalThis")), ...(await target.lexicalNames())]);
|
|
658
|
-
const matches = [...names].filter((name) => name.startsWith(expr) && !name.startsWith("__cdpk")).sort();
|
|
659
|
-
return { matches, start: cursor - expr.length };
|
|
660
|
-
}
|
|
661
|
-
async #requestInput(prompt, password) {
|
|
662
|
-
// The target asked while an interrupt was already waiting on it.
|
|
663
|
-
if (this.#interrupting)
|
|
664
|
-
return { value: null, interrupted: true };
|
|
665
|
-
const parent = this.#parent;
|
|
666
|
-
if (!this.#allowStdin || typeof parent.msg_id !== "string")
|
|
667
|
-
return NO_INPUT;
|
|
668
|
-
if (this.#stdin.peers.size === 0)
|
|
669
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
670
|
-
if (this.#stdin.peers.size === 0)
|
|
671
|
-
return NO_INPUT;
|
|
672
|
-
const header = makeHeader(this.#session, "input_request");
|
|
673
|
-
const answer = new Promise((settle) => (this.#input = { msgId: header.msg_id, settle }));
|
|
674
|
-
this.#stdin.sendAll(encodeMessage(this.#session, this.#conn.key, "input_request", { prompt, password }, {
|
|
675
|
-
header,
|
|
676
|
-
parentHeader: parent,
|
|
677
|
-
}));
|
|
678
|
-
return answer;
|
|
679
|
-
}
|
|
680
|
-
/** stdin carries only input_replys. */
|
|
681
|
-
async #handleStdin(reply) {
|
|
682
|
-
if (reply.header.msg_type !== "input_reply")
|
|
683
|
-
return;
|
|
684
|
-
const input = this.#input;
|
|
685
|
-
// A reply with nothing waiting is late (its prompt was interrupted), and
|
|
686
|
-
// one naming another request is for an earlier prompt: drop both rather
|
|
687
|
-
// than answer the next prompt with them (#121). Some clients
|
|
688
|
-
// (jupyter_client's input()) send no parent header, so only a present
|
|
689
|
-
// one is checked.
|
|
690
|
-
if (input === null)
|
|
691
|
-
return;
|
|
692
|
-
const parentId = reply.parentHeader.msg_id;
|
|
693
|
-
if (typeof parentId === "string" && parentId !== "" && parentId !== input.msgId)
|
|
694
|
-
return;
|
|
695
|
-
this.#input = null;
|
|
696
|
-
input.settle({ value: typeof reply.content.value === "string" ? reply.content.value : null, interrupted: false });
|
|
697
|
-
}
|
|
698
|
-
async #evaluateCell(target, code) {
|
|
699
|
-
// Object-literal wrapping (Deno's evaluate_line_with_object_wrapping):
|
|
700
|
-
// try `({ ... })` first and fall back to the code as written.
|
|
701
|
-
// While debugging, cells carry the path JupyterLab gave them and keep a
|
|
702
|
-
// line map, so breakpoints bind and frames point at the cell (#18).
|
|
703
|
-
const debugging = this.#debugger?.started === true && target.debug !== undefined;
|
|
704
|
-
const transform = (source) => debugging && this.#debugger ? this.#debugger.cellScript(code, source) : target.transform(source, this.#transformOptions());
|
|
705
|
-
const wrapped = objectLiteralCandidate(code);
|
|
706
|
-
if (wrapped !== null) {
|
|
707
|
-
try {
|
|
708
|
-
const result = await target.evaluate(transform(wrapped));
|
|
709
|
-
if (!result.exceptionDetails)
|
|
710
|
-
return result;
|
|
711
|
-
}
|
|
712
|
-
catch (err) {
|
|
713
|
-
if (!(err instanceof CellSyntaxError))
|
|
714
|
-
throw err;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
return target.evaluate(transform(code));
|
|
718
|
-
}
|
|
719
|
-
async #execute(msg, peer) {
|
|
720
|
-
const parent = msg.header;
|
|
721
|
-
if (this.#aborting) {
|
|
722
|
-
// Queued behind a failed cell: not run, not counted (#123).
|
|
723
|
-
this.#status("busy", parent);
|
|
724
|
-
this.#reply(this.#shell, peer, msg, "execute_reply", { status: "aborted" });
|
|
725
|
-
this.#status("idle", parent);
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
const code = String(msg.content.code ?? "");
|
|
729
|
-
const silent = msg.content.silent === true;
|
|
730
|
-
if (!silent && msg.content.store_history !== false) {
|
|
731
|
-
this.#executionCount++;
|
|
732
|
-
this.#history.push({ line: this.#executionCount, input: code });
|
|
733
|
-
}
|
|
734
|
-
const count = this.#executionCount;
|
|
735
|
-
this.#parent = parent;
|
|
736
|
-
this.#allowStdin = msg.content.allow_stdin === true;
|
|
737
|
-
this.#status("busy", parent);
|
|
738
|
-
// Silent requests broadcast nothing on iopub but status (#124).
|
|
739
|
-
if (!silent)
|
|
740
|
-
this.#publish("execute_input", { code, execution_count: count });
|
|
741
|
-
if (this.#projectWarning !== null) {
|
|
742
|
-
// Shown once, in the first cell, where the user will see it.
|
|
743
|
-
this.#publish("stream", { name: "stderr", text: `[cdp-kernel] ${this.#projectWarning}\n` });
|
|
744
|
-
this.#projectWarning = null;
|
|
745
|
-
}
|
|
746
|
-
let error = null;
|
|
747
|
-
this.#executing = true;
|
|
748
|
-
try {
|
|
749
|
-
const target = await this.#ensureTarget();
|
|
750
|
-
if (target.notice !== undefined && this.#noticeShownFor !== target) {
|
|
751
|
-
// Once per target, in the first cell it runs.
|
|
752
|
-
this.#noticeShownFor = target;
|
|
753
|
-
this.#publish("stream", { name: "stderr", text: `${target.notice}\n` });
|
|
754
|
-
}
|
|
755
|
-
const result = await this.#evaluateCell(target, code);
|
|
756
|
-
// It ran, so its declarations exist, even if it threw part way.
|
|
757
|
-
this.#types?.addCell(code);
|
|
758
|
-
if (result.exceptionDetails) {
|
|
759
|
-
const exception = result.exceptionDetails.exception;
|
|
760
|
-
const described = exception
|
|
761
|
-
? await target.describeError(exception)
|
|
762
|
-
: { name: "Error", message: result.exceptionDetails.text, stack: "" };
|
|
763
|
-
error = {
|
|
764
|
-
ename: described.name || "Error",
|
|
765
|
-
evalue: described.message,
|
|
766
|
-
traceback: (described.stack || `${described.name}: ${described.message}`).split("\n"),
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
else if (!silent && result.result.type !== "undefined") {
|
|
770
|
-
await target.broadcastResult(count, result.result);
|
|
771
|
-
}
|
|
772
|
-
await target.flush();
|
|
773
|
-
}
|
|
774
|
-
catch (err) {
|
|
775
|
-
if (err instanceof CellSyntaxError) {
|
|
776
|
-
// Deno reports these as an opaque "Execution failed".
|
|
777
|
-
error = { ename: "SyntaxError", evalue: err.message, traceback: [`SyntaxError: ${err.message}`] };
|
|
778
|
-
}
|
|
779
|
-
else if (err instanceof Interrupted) {
|
|
780
|
-
error = { ename: "Interrupted", evalue: "Execution interrupted", traceback: ["Execution interrupted"] };
|
|
781
|
-
await this.#target?.flush().catch(() => { });
|
|
782
|
-
}
|
|
783
|
-
else {
|
|
784
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
785
|
-
error = { ename: "KernelError", evalue: message, traceback: [message] };
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
finally {
|
|
789
|
-
this.#executing = false;
|
|
790
|
-
}
|
|
791
|
-
// The result and error are shown: nothing refers to them any more (#286).
|
|
792
|
-
await this.#target?.releaseResults?.();
|
|
793
|
-
if (error) {
|
|
794
|
-
if (!silent && msg.content.stop_on_error !== false) {
|
|
795
|
-
// Abort what was queued behind this cell (JupyterLab's Run All sends
|
|
796
|
-
// every cell at once), but nothing sent after the client sees the
|
|
797
|
-
// error. A macrotask first lets bytes already received be parsed.
|
|
798
|
-
await new Promise((r) => setTimeout(r, 0));
|
|
799
|
-
this.#abortQueued = this.#shell.incoming.size;
|
|
800
|
-
}
|
|
801
|
-
this.#publish("error", error);
|
|
802
|
-
this.#reply(this.#shell, peer, msg, "execute_reply", { status: "error", execution_count: count, ...error });
|
|
803
|
-
}
|
|
804
|
-
else {
|
|
805
|
-
this.#reply(this.#shell, peer, msg, "execute_reply", {
|
|
806
|
-
status: "ok",
|
|
807
|
-
execution_count: count,
|
|
808
|
-
payload: [],
|
|
809
|
-
user_expressions: {},
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
this.#status("idle", parent);
|
|
180
|
+
process.on("SIGUSR2", () => void core.logState());
|
|
181
|
+
await core.run();
|
|
813
182
|
}
|
|
814
183
|
}
|