@tlockney/cdp-kernel 0.7.2 → 0.8.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.
@@ -60,7 +60,8 @@ export declare class BrowserTarget implements KernelTarget {
60
60
  * A cell that's waiting on something leaves the Worker free: it answers a
61
61
  * ping, and the cell is abandoned with its state intact; host requests
62
62
  * in flight are cancelled on the page, and reject in the Worker with an
63
- * AbortError (#388). A busy cell can't be reached, so the Worker is
63
+ * AbortError (#388), and jupyter.signal is aborted (#418). A busy cell
64
+ * can't be reached, so the Worker is
64
65
  * terminated, and a new one starts on the next cell.
65
66
  */
66
67
  interrupt(): Promise<void>;
@@ -27,7 +27,10 @@ export class BrowserTarget {
27
27
  #hostCalls = new Map();
28
28
  #host;
29
29
  #nextId = 0;
30
- #interrupted = false;
30
+ /** Counts interrupt() calls: a call that saw one start during it was interrupted (#417). */
31
+ #interrupts = 0;
32
+ /** Cells running: a comm handler that starts with none gets its own jupyter.signal (#421). */
33
+ #cells = 0;
31
34
  #stopNote;
32
35
  #exited = false;
33
36
  constructor(worker, events, host) {
@@ -148,28 +151,37 @@ export class BrowserTarget {
148
151
  });
149
152
  }
150
153
  async #interruptibly(op, params, what) {
151
- this.#interrupted = false;
154
+ const interrupts = this.#interrupts;
152
155
  try {
153
156
  return await this.#request(op, params, true);
154
157
  }
155
158
  catch (err) {
156
- if (err instanceof Abandoned || this.#interrupted)
159
+ if (err instanceof Abandoned || this.#interrupts !== interrupts)
157
160
  throw new Interrupted(`${what} interrupted`);
158
161
  throw err;
159
162
  }
160
163
  }
161
164
  async evaluate(script) {
162
- return asEvaluateResult(await this.#interruptibly("eval", { code: script }, "Execution"));
165
+ this.#cells++;
166
+ try {
167
+ return asEvaluateResult(await this.#interruptibly("eval", { code: script }, "Execution"));
168
+ }
169
+ finally {
170
+ this.#cells--;
171
+ }
163
172
  }
164
173
  /**
165
174
  * A cell that's waiting on something leaves the Worker free: it answers a
166
175
  * ping, and the cell is abandoned with its state intact; host requests
167
176
  * in flight are cancelled on the page, and reject in the Worker with an
168
- * AbortError (#388). A busy cell can't be reached, so the Worker is
177
+ * AbortError (#388), and jupyter.signal is aborted (#418). A busy cell
178
+ * can't be reached, so the Worker is
169
179
  * terminated, and a new one starts on the next cell.
170
180
  */
171
181
  async interrupt() {
172
- this.#interrupted = true;
182
+ this.#interrupts++;
183
+ // Before abandoning the cell's request, which ends the count.
184
+ const cellRunning = this.#cells > 0;
173
185
  const running = [...this.#interruptible];
174
186
  const answered = await Promise.race([
175
187
  this.#request("ping").then(() => true, () => false),
@@ -182,6 +194,10 @@ export class BrowserTarget {
182
194
  this.#interruptible.delete(id);
183
195
  }
184
196
  this.#cancelHostCalls();
197
+ // After the cell is abandoned, so its reply can't end it first
198
+ // (#418). Not awaited: requests are answered in order, and an abort
199
+ // listener that never returns mustn't hold up the interrupt.
200
+ void this.#request("abort", { cell: cellRunning }).catch(() => { });
185
201
  return;
186
202
  }
187
203
  this.#stopNote = RESTART_NOTE;
@@ -197,7 +213,13 @@ export class BrowserTarget {
197
213
  await this.#request("broadcast", { count: executionCount, handle: result.objectId });
198
214
  }
199
215
  async dispatchComm(msgType, content, metadata, buffers) {
200
- const reply = await this.#interruptibly("comm", { msgType, content, metadata, buffers: buffers.map(toBase64) }, "Comm handler");
216
+ const reply = await this.#interruptibly("comm", {
217
+ msgType,
218
+ content,
219
+ metadata,
220
+ buffers: buffers.map(toBase64),
221
+ ownSignal: this.#cells === 0,
222
+ }, "Comm handler");
201
223
  if (isRecord(reply.error)) {
202
224
  const { name, message, stack } = reply.error;
203
225
  return { error: { name: String(name), message: String(message), stack: String(stack) } };
@@ -604,8 +604,35 @@ function createJupyter(host2) {
604
604
  return answer2 === "" ? defaultValue : answer2;
605
605
  }
606
606
  const comms = createComms((msgType, content, metadata, buffers) => host2.send(msgType, content, metadata, buffers));
607
+ let cell = new AbortController();
608
+ let current = cell;
609
+ const handlers = /* @__PURE__ */ new Map();
610
+ function startCell() {
611
+ current = cell = new AbortController();
612
+ }
613
+ function abortSignal(cellRunning = true) {
614
+ const reason = new DOMException("Execution interrupted", "AbortError");
615
+ if (cellRunning) cell.abort(reason);
616
+ for (const controller of handlers.keys()) controller.abort(reason);
617
+ handlers.clear();
618
+ }
619
+ async function commDispatch(msgType, content, metadata, buffers, ownSignal = false) {
620
+ const controller = ownSignal ? new AbortController() : cell;
621
+ if (ownSignal) current = controller;
622
+ handlers.set(controller, (handlers.get(controller) ?? 0) + 1);
623
+ try {
624
+ return await comms.dispatch(msgType, content, metadata, buffers);
625
+ } finally {
626
+ const left = (handlers.get(controller) ?? 0) - 1;
627
+ if (left > 0) handlers.set(controller, left);
628
+ else handlers.delete(controller);
629
+ }
630
+ }
607
631
  return {
608
632
  namespace: {
633
+ get signal() {
634
+ return current.signal;
635
+ },
609
636
  broadcast,
610
637
  display,
611
638
  format: format2,
@@ -621,7 +648,9 @@ function createJupyter(host2) {
621
648
  formatNow,
622
649
  confirm,
623
650
  prompt,
624
- commDispatch: comms.dispatch
651
+ commDispatch,
652
+ startCell,
653
+ abortSignal
625
654
  };
626
655
  }
627
656
 
@@ -653,6 +682,7 @@ function createHandler(shim, evaluate = (code) => (0, eval)(code)) {
653
682
  return {};
654
683
  case "eval": {
655
684
  handles.clear();
685
+ shim.startCell?.();
656
686
  try {
657
687
  const box = await evaluate(request.code);
658
688
  return { result: box === void 0 ? { type: "undefined" } : remote(box[0]) };
@@ -671,7 +701,13 @@ function createHandler(shim, evaluate = (code) => (0, eval)(code)) {
671
701
  return { value: shim.describeError(take(request.handle)) };
672
702
  case "comm":
673
703
  try {
674
- const handled = await shim.commDispatch(request.msgType, request.content, request.metadata, request.buffers);
704
+ const handled = await shim.commDispatch(
705
+ request.msgType,
706
+ request.content,
707
+ request.metadata,
708
+ request.buffers,
709
+ request.ownSignal === true
710
+ );
675
711
  return { handled: handled === true };
676
712
  } catch (err) {
677
713
  return { error: JSON.parse(shim.describeError(err)) };
@@ -693,6 +729,9 @@ function createHandler(shim, evaluate = (code) => (0, eval)(code)) {
693
729
  return { text: shim.inspectPath(request.path, request.detail ?? 0) };
694
730
  case "barrier":
695
731
  return { value: shim.barrier() };
732
+ case "abort":
733
+ shim.abortSignal?.(request.cell !== false);
734
+ return {};
696
735
  default:
697
736
  throw new Error(`unknown request ${JSON.stringify(request.op)}`);
698
737
  }
@@ -1034,9 +1073,13 @@ function install({ base, cdn: cdnBase }) {
1034
1073
  broadcastResult: jupyter.broadcastResult,
1035
1074
  // execute_request's user_expressions (#340).
1036
1075
  formatJson: async (value) => JSON.stringify(await jupyter.namespace.format(value)),
1037
- async commDispatch(msgType, content, metadata, buffers64) {
1076
+ // jupyter.signal (#418, #421): a new one as each cell starts; the host
1077
+ // aborts the running ones on interrupt.
1078
+ startCell: jupyter.startCell,
1079
+ abortSignal: jupyter.abortSignal,
1080
+ async commDispatch(msgType, content, metadata, buffers64, ownSignal) {
1038
1081
  const buffers = buffers64.map((b) => Uint8Array.from(atob(b), (c) => c.charCodeAt(0)));
1039
- return await jupyter.commDispatch(msgType, content, metadata, buffers);
1082
+ return await jupyter.commDispatch(msgType, content, metadata, buffers, ownSignal);
1040
1083
  },
1041
1084
  describeError(value) {
1042
1085
  if (value instanceof Error) {