knitting 0.1.60 → 0.1.62

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.
Files changed (46) hide show
  1. package/BROWSER.md +134 -0
  2. package/README.md +94 -6
  3. package/knitting.browser.d.ts +6 -0
  4. package/knitting.browser.js +1 -0
  5. package/package.json +11 -2
  6. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  7. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  8. package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
  9. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  10. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  11. package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
  12. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  13. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  14. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  15. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  16. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  17. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  18. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  19. package/src/api.js +136 -25
  20. package/src/common/runtime.d.ts +2 -1
  21. package/src/common/runtime.js +8 -1
  22. package/src/common/task-source.d.ts +9 -0
  23. package/src/common/task-source.js +15 -0
  24. package/src/memory/lock.d.ts +37 -1
  25. package/src/memory/lock.js +262 -5
  26. package/src/memory/payloadCodec.js +2 -6
  27. package/src/memory/regionRegistry.d.ts +1 -3
  28. package/src/memory/regionRegistry.js +16 -14
  29. package/src/permission/protocol.js +5 -1
  30. package/src/runtime/dispatcher.d.ts +11 -8
  31. package/src/runtime/dispatcher.js +163 -21
  32. package/src/runtime/inline-executor.js +6 -1
  33. package/src/runtime/pool.d.ts +127 -4
  34. package/src/runtime/pool.js +262 -73
  35. package/src/runtime/process-worker.d.ts +24 -12
  36. package/src/runtime/process-worker.js +185 -115
  37. package/src/runtime/tx-queue.d.ts +17 -1
  38. package/src/runtime/tx-queue.js +120 -15
  39. package/src/runtime/worker-common.d.ts +13 -0
  40. package/src/runtime/worker-common.js +103 -0
  41. package/src/types.d.ts +75 -4
  42. package/src/worker/loop.js +34 -5
  43. package/src/worker/rx-queue.d.ts +11 -1
  44. package/src/worker/rx-queue.js +4 -1
  45. package/src/worker/timers.d.ts +8 -1
  46. package/src/worker/timers.js +20 -17
@@ -1,5 +1,34 @@
1
1
  import { createRuntimeMessageChannel, } from "../common/worker-runtime.js";
2
- export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, }, queue: { completeFrame, hasPendingFrames, flushToWorker, txIdle, }, channelHandler, dispatcherOptions, notifySignal, }) => {
2
+ import { RUNTIME, SET_IMMEDIATE } from "../common/runtime.js";
3
+ /**
4
+ * Macrotask primitive for the host pump, picked per runtime: a round trip costs
5
+ * 757ns on Bun via MessageChannel but 4662ns on Deno, where `setImmediate` is
6
+ * 1110ns. Bun keeps the channel; browser and Andromeda have no choice.
7
+ *
8
+ * `serial-channel` opts back into the channel explicitly (see `src/api.ts`): one
9
+ * hop drives every lane's check in turn and relies on the channel's delivery to
10
+ * do it, so a merely cheaper pump starves it -- it cost 25% throughput there.
11
+ */
12
+ const IMMEDIATE_PUMP = RUNTIME === "deno" || RUNTIME === "node"
13
+ ? SET_IMMEDIATE
14
+ : undefined;
15
+ /**
16
+ * Free drain hops before `scheduleNotify` stops re-arming the pump for free.
17
+ *
18
+ * The window has to match what the dispatcher escalates *to*, so these are
19
+ * chosen together and `canUseDoorbell` picks between them. Polling escalates to
20
+ * the `setTimeout` ladder, whose finest rung is ~1.1ms on every runtime, so it
21
+ * wants a wide window; that sleep is also load-bearing, since it batches
22
+ * completions. A doorbell escalates to `Atomics.waitAsync` at about the price of
23
+ * one hop, so it wants a narrow one.
24
+ *
25
+ * Mixing them is the trap: a doorbell behind the wide window keeps every poll
26
+ * hop *and* adds the arm, and loses to plain polling at every thread count.
27
+ * Measurements behind both values: `docs/host-doorbell-proposal.md`.
28
+ */
29
+ const POLL_STALL_FREE_LOOPS = 128;
30
+ const DOORBELL_STALL_FREE_LOOPS = 1;
31
+ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, }, queue: { completeFrame, hasPendingFrames, flushToWorker, txIdle, waitForCompletion, setCompletionWaiterArmed, }, channelHandler, dispatcherOptions, notifySignal, crossProcess, }) => {
3
32
  const a_load = Atomics.load;
4
33
  const a_store = Atomics.store;
5
34
  const a_notify = Atomics.notify;
@@ -10,13 +39,81 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
10
39
  a_notify(opView, 0, 1);
11
40
  });
12
41
  const notify = () => channelHandler.notify();
42
+ // `crossProcess` is a capability, not a preference, so it overrides an
43
+ // explicit `doorbell: true`: V8's Atomics waiter list is per isolate, so a
44
+ // worker in another process can never ring the host's waiter, and an armed
45
+ // doorbell would just sleep to the watchdog.
46
+ const canUseDoorbell = crossProcess !== true &&
47
+ (dispatcherOptions?.doorbell ?? true) &&
48
+ (RUNTIME === "bun" || RUNTIME === "node") &&
49
+ typeof Atomics.waitAsync === "function";
50
+ let doorbellEnabled = canUseDoorbell;
51
+ let doorbellArmed = false;
52
+ let doorbellEpoch = 0 | 0;
53
+ const DOORBELL_WATCHDOG_MS = 1000;
13
54
  let stallCount = 0 | 0;
14
- const STALL_FREE_LOOPS = Math.max(0, (dispatcherOptions?.stallFreeLoops ?? 128) | 0);
55
+ const requestedStallFreeLoops = dispatcherOptions?.stallFreeLoops;
56
+ let stallFreeLoops = requestedStallFreeLoops !== undefined
57
+ ? Math.max(0, requestedStallFreeLoops | 0)
58
+ : canUseDoorbell
59
+ ? DOORBELL_STALL_FREE_LOOPS
60
+ : POLL_STALL_FREE_LOOPS;
15
61
  const MAX_BACKOFF_MS = Math.max(0, (dispatcherOptions?.maxBackoffMs ?? 10) | 0);
16
62
  let backoffTimer;
17
63
  // inFlight prevents re-entrancy when pool.ts fires check() concurrently
18
64
  // from both send() and the channel callback. Cheaper than try/finally.
19
65
  let inFlight = false;
66
+ const cancelDoorbell = () => {
67
+ if (!doorbellArmed)
68
+ return;
69
+ doorbellEpoch = (doorbellEpoch + 1) | 0;
70
+ doorbellArmed = false;
71
+ setCompletionWaiterArmed(false);
72
+ };
73
+ const armDoorbell = () => {
74
+ if (!doorbellEnabled || doorbellArmed === true) {
75
+ if (!doorbellEnabled)
76
+ notify();
77
+ return;
78
+ }
79
+ const token = (doorbellEpoch + 1) | 0;
80
+ doorbellEpoch = token;
81
+ doorbellArmed = true;
82
+ let woke = false;
83
+ const wake = () => {
84
+ if (!doorbellArmed || doorbellEpoch !== token || woke)
85
+ return;
86
+ woke = true;
87
+ doorbellArmed = false;
88
+ doorbellEpoch = (doorbellEpoch + 1) | 0;
89
+ setCompletionWaiterArmed(false);
90
+ // Keep the existing macrotask boundary. Calling check directly from a
91
+ // resolved waitAsync promise can chain microtasks under a hot workload
92
+ // and starve host I/O.
93
+ notify();
94
+ };
95
+ let supported = false;
96
+ try {
97
+ supported = waitForCompletion(wake, DOORBELL_WATCHDOG_MS);
98
+ }
99
+ catch {
100
+ supported = false;
101
+ }
102
+ // Some runtimes reject waitAsync on their SharedArrayBuffer variants; fall
103
+ // back to polling rather than hanging the pool.
104
+ if (!supported) {
105
+ doorbellEnabled = false;
106
+ // The window was narrowed for a doorbell that no longer exists; widen it
107
+ // back or the ladder fires after a single fruitless drain.
108
+ if (requestedStallFreeLoops === undefined) {
109
+ stallFreeLoops = POLL_STALL_FREE_LOOPS;
110
+ }
111
+ doorbellArmed = false;
112
+ doorbellEpoch = (doorbellEpoch + 1) | 0;
113
+ setCompletionWaiterArmed(false);
114
+ notify();
115
+ }
116
+ };
20
117
  const check = () => {
21
118
  if (inFlight) {
22
119
  // Another check() is already mid-drain; mark that a re-run is needed
@@ -24,6 +121,16 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
24
121
  check.rerun = true;
25
122
  return;
26
123
  }
124
+ // A waitAsync cannot be cancelled, so bumping its epoch makes the eventual
125
+ // callback inert instead.
126
+ cancelDoorbell();
127
+ // Idle lane: skip the drain. Otherwise it pays a txStatus write, an rxStatus
128
+ // load, and an Atomics.notify that wakes a parked worker for nothing — the
129
+ // waste that makes serial-channel latency grow with thread count.
130
+ if (txIdle()) {
131
+ check.isRunning = false;
132
+ return;
133
+ }
27
134
  inFlight = true;
28
135
  if (backoffTimer !== undefined) {
29
136
  clearTimeout(backoffTimer);
@@ -37,25 +144,27 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
37
144
  a_store(opView, 0, 1);
38
145
  wakeSignal();
39
146
  }
40
- // Drain loop: local vars so V8 keeps them as unboxed int32.
41
- let anyProgressed = false;
147
+ // Local vars so V8 keeps them as unboxed int32. Only a reaped completion
148
+ // counts as progress for `stallCount`: the pump exists to notice work a
149
+ // worker cannot announce, and letting a flush reset the counter would
150
+ // leave the escalation unreachable for a pool that never runs dry.
151
+ let completed = false;
42
152
  let progressed = true;
43
153
  while (progressed) {
44
154
  progressed = false;
45
155
  if (completeFrame() > 0) {
46
156
  progressed = true;
47
- anyProgressed = true;
157
+ completed = true;
48
158
  }
49
159
  while (hasPendingFrames()) {
50
160
  if (!flushToWorker())
51
161
  break;
52
162
  progressed = true;
53
- anyProgressed = true;
54
163
  }
55
164
  }
56
165
  txStatus[0] = 0;
57
166
  if (!txIdle()) {
58
- if (anyProgressed || hasPendingFrames()) {
167
+ if (completed || hasPendingFrames()) {
59
168
  stallCount = 0 | 0;
60
169
  }
61
170
  else {
@@ -74,14 +183,22 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
74
183
  check.isRunning = false;
75
184
  check.rerun = false;
76
185
  const scheduleNotify = () => {
77
- if (stallCount <= STALL_FREE_LOOPS) {
186
+ if (stallCount <= stallFreeLoops) {
78
187
  notify();
79
188
  return;
80
189
  }
190
+ if (doorbellEnabled) {
191
+ // Release isRunning during the wait so a fresh send() can restart the
192
+ // dispatcher immediately. The epoch in cancelDoorbell() invalidates the
193
+ // old waiter in that case.
194
+ check.isRunning = false;
195
+ armDoorbell();
196
+ return;
197
+ }
81
198
  // One delayed wakeup at a time; fresh send() calls preempt via check directly.
82
199
  if (backoffTimer !== undefined)
83
200
  return;
84
- let delay = (stallCount - STALL_FREE_LOOPS - 1) | 0;
201
+ let delay = (stallCount - stallFreeLoops - 1) | 0;
85
202
  if (delay < 0)
86
203
  delay = 0;
87
204
  else if (delay > MAX_BACKOFF_MS)
@@ -99,25 +216,47 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
99
216
  return { check };
100
217
  };
101
218
  export class ChannelHandler {
219
+ // Set only when the pump is a MessageChannel. The `setImmediate` pump has no
220
+ // ports, and nothing outside this class reads them.
102
221
  channel;
103
222
  port1;
104
223
  port2;
105
- #post2;
106
- constructor() {
107
- this.channel = createRuntimeMessageChannel();
108
- this.port1 = this.channel.port1;
109
- this.port2 = this.channel.port2;
110
- this.#post2 = (message) => this.port2.postMessage(message);
224
+ #handler;
225
+ #notify;
226
+ constructor(pump = "auto") {
227
+ if (pump === "auto" && IMMEDIATE_PUMP !== undefined) {
228
+ const immediate = IMMEDIATE_PUMP;
229
+ // Allocated once; the pump fires hundreds of thousands of times a second.
230
+ // Routing via `#handler` also makes a callback outliving `close()` inert.
231
+ const run = () => {
232
+ this.#handler?.();
233
+ };
234
+ this.#notify = () => {
235
+ immediate(run);
236
+ };
237
+ return;
238
+ }
239
+ const channel = createRuntimeMessageChannel();
240
+ const port2 = channel.port2;
241
+ this.channel = channel;
242
+ this.port1 = channel.port1;
243
+ this.port2 = port2;
244
+ this.#notify = () => {
245
+ port2.postMessage(null);
246
+ };
111
247
  }
112
248
  notify() {
113
- this.#post2(null);
249
+ this.#notify();
114
250
  }
115
251
  /**
116
- * Opens the channel (if not already open) and sets the onmessage handler.
117
- * This is the setup so `notify` can send a message to the port 1.
252
+ * Registers the handler the pump calls back into. On the channel pump this is
253
+ * also where the ports are opened, so `notify` can reach port 1.
118
254
  */
119
255
  open(f) {
256
+ this.#handler = f;
120
257
  const port1 = this.port1;
258
+ if (port1 === undefined)
259
+ return;
121
260
  if (typeof port1.on === "function") {
122
261
  port1.on("message", f);
123
262
  }
@@ -125,13 +264,16 @@ export class ChannelHandler {
125
264
  // @ts-ignore
126
265
  port1.onmessage = f;
127
266
  }
128
- this.port1.start?.();
129
- this.port2.start?.();
267
+ this.port1?.start?.();
268
+ this.port2?.start?.();
130
269
  }
131
270
  /**
132
- * Closes the channel if it is open.
271
+ * Detaches the handler, and closes the channel if this pump has one.
133
272
  */
134
273
  close() {
274
+ this.#handler = undefined;
275
+ if (this.port1 === undefined || this.port2 === undefined)
276
+ return;
135
277
  //@ts-ignore
136
278
  this.port1.onmessage = null;
137
279
  //@ts-ignore
@@ -81,9 +81,14 @@ const composeInlineCallable = (fn, timeout, useAbortToolkit = false) => {
81
81
  };
82
82
  };
83
83
  export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
84
+ // `createPool` passes an already name-sorted array, which is the order
85
+ // `fnNumber` indexes into. The object form has to reach the same order on its
86
+ // own: task ids identify a task, they do not rank it, so sorting by id would
87
+ // hand callers a different lane than the one they addressed.
84
88
  const entries = Array.isArray(tasks)
85
89
  ? tasks
86
- : Object.values(tasks).sort((a, b) => a.id - b.id);
90
+ : Object.values(tasks)
91
+ .sort((a, b) => a.name.localeCompare(b.name));
87
92
  const runners = entries.map((entry) => {
88
93
  // Imported tasks must never execute on the host inline lane: their module
89
94
  // import is meant to stay inside the worker so worker permission policies
@@ -1,12 +1,115 @@
1
- import { type ProcessSharedMemoryBacking } from "./process-worker.js";
1
+ import { createHostTxQueue } from "./tx-queue.js";
2
+ import { type ProcessSharedMemoryBacking, type ProcessStealMemoryLayout, type ResolvedProcessSharedMemorySettings } from "./process-worker.js";
2
3
  import { type Sab } from "../ipc/transport/shared-memory.js";
3
4
  import { ChannelHandler, type DispatcherCheck } from "./dispatcher.js";
4
- import { lock2 } from "../memory/lock.js";
5
+ import { lock2, type Task } from "../memory/lock.js";
5
6
  import "../memory/payloadCodec.js";
6
- import type { DebugOptions, DispatcherSettings, WorkerContext, WorkerData, WorkerSettings } from "../types.js";
7
+ import type { DebugOptions, DispatcherSettings, LockBuffers, WorkerContext, WorkerData, WorkerSettings } from "../types.js";
7
8
  import "../worker/loop.js";
8
9
  import { type PayloadBufferOptions } from "../memory/payload-config.js";
9
- export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug, hostDebug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, abortSignalCapacity, usesAbortSignal, sharedChannelHandler, }: {
10
+ /**
11
+ * Build the buffers, host-side locks and pending registry that a stealing pool
12
+ * shares. One submit region for every worker to claim from, one private return
13
+ * region per worker, and a single pool-global queue so a response arriving on
14
+ * any lane settles the right promise.
15
+ *
16
+ * Region width follows paper §6.1: the largest power-of-two `g` leaving a spare
17
+ * region for a delayed claimant (`slots / g >= consumers + 1`). At 32 slots that
18
+ * caps how wide a region can be well before it caps the worker count.
19
+ */
20
+ export declare const resolveStealRegionLanes: (consumers: number) => number;
21
+ /** Maximum claimants that leave the protocol's required spare region. */
22
+ export declare const MAX_STEAL_CONSUMERS: number;
23
+ export declare const createStealPoolBuffers: ({ threads, payload, regionLanes, abortSignalCapacity, usesAbortSignal, processWorker, }: {
24
+ threads: number;
25
+ payload?: PayloadBufferOptions;
26
+ regionLanes?: number;
27
+ abortSignalCapacity?: number;
28
+ usesAbortSignal?: boolean;
29
+ processWorker?: {
30
+ signalBytes: number;
31
+ sharedMemory: ResolvedProcessSharedMemorySettings;
32
+ };
33
+ }) => {
34
+ submitBuffers: LockBuffers;
35
+ returnBuffers: LockBuffers[];
36
+ hostSubmitLock: {
37
+ enlist: (task: Task) => true;
38
+ encode: (task: Task, state?: number) => boolean;
39
+ encodeManyFrom: (list: import("../ipc/tools/ring-queue.js").default<Task>) => number;
40
+ encodeAll: () => boolean;
41
+ publish: (task: Task) => boolean;
42
+ flushPending: () => boolean;
43
+ decode: () => boolean;
44
+ hasSpace: () => boolean;
45
+ resolved: import("../ipc/tools/ring-queue.js").default<Task>;
46
+ hostBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
47
+ workerBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
48
+ recyclecList: import("../ipc/tools/ring-queue.js").default<Task>;
49
+ resolveHost: ({ queue, onResolved, shouldSettle, activeRejectPlaceholder, }: {
50
+ queue: Task[];
51
+ onResolved?: (task: Task) => void;
52
+ shouldSettle?: (task: Task) => boolean;
53
+ activeRejectPlaceholder?: Task["reject"];
54
+ }) => () => number;
55
+ waitForHostChange: (timeoutMs?: number) => import("../memory/lock.js").WaitAsyncResult | undefined;
56
+ setHostWaiterArmed: (armed: boolean) => void;
57
+ hasPendingFrames: () => boolean;
58
+ getPendingFrameCount: () => number;
59
+ getPendingPromiseCount: () => number;
60
+ resetPendingState: () => void;
61
+ deactivateStealConsumer: (id: number) => boolean;
62
+ takeDeferredCount: () => number;
63
+ setPromiseHandler: (handler?: import("../memory/lock.js").PromisePayloadHandler) => void;
64
+ };
65
+ hostReturnLocks: {
66
+ enlist: (task: Task) => true;
67
+ encode: (task: Task, state?: number) => boolean;
68
+ encodeManyFrom: (list: import("../ipc/tools/ring-queue.js").default<Task>) => number;
69
+ encodeAll: () => boolean;
70
+ publish: (task: Task) => boolean;
71
+ flushPending: () => boolean;
72
+ decode: () => boolean;
73
+ hasSpace: () => boolean;
74
+ resolved: import("../ipc/tools/ring-queue.js").default<Task>;
75
+ hostBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
76
+ workerBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
77
+ recyclecList: import("../ipc/tools/ring-queue.js").default<Task>;
78
+ resolveHost: ({ queue, onResolved, shouldSettle, activeRejectPlaceholder, }: {
79
+ queue: Task[];
80
+ onResolved?: (task: Task) => void;
81
+ shouldSettle?: (task: Task) => boolean;
82
+ activeRejectPlaceholder?: Task["reject"];
83
+ }) => () => number;
84
+ waitForHostChange: (timeoutMs?: number) => import("../memory/lock.js").WaitAsyncResult | undefined;
85
+ setHostWaiterArmed: (armed: boolean) => void;
86
+ hasPendingFrames: () => boolean;
87
+ getPendingFrameCount: () => number;
88
+ getPendingPromiseCount: () => number;
89
+ resetPendingState: () => void;
90
+ deactivateStealConsumer: (id: number) => boolean;
91
+ takeDeferredCount: () => number;
92
+ setPromiseHandler: (handler?: import("../memory/lock.js").PromisePayloadHandler) => void;
93
+ }[];
94
+ sharedQueue: {
95
+ rejectAll: (reason: string) => void;
96
+ hasPendingFrames: () => boolean;
97
+ txIdle: () => boolean;
98
+ completeFrame: () => number;
99
+ waitForCompletion: (onWake: () => void, timeoutMs?: number) => boolean;
100
+ setCompletionWaiterArmed: (armed: boolean) => void;
101
+ setReturnHooks: (lane: number, hooks: (import("../connections/buffer-reference.js").BufferReferenceReturnHooks | ((token: bigint) => void)) | undefined) => void;
102
+ enqueue: (functionID: number, timeout?: import("../types.js").TaskTimeout, abortSignal?: import("../types.js").AbortSignalOption) => (rawArgs: unknown) => Promise<never> | import("../common/with-resolvers.js").PromiseWithMaybeReject<unknown>;
103
+ flushToWorker: () => boolean;
104
+ enqueueKnown: (task: Task) => boolean;
105
+ settlePromisePayload: (task: Task, isRejected: boolean, value: unknown) => boolean;
106
+ };
107
+ regionLanes: number;
108
+ processMemory: ProcessStealMemoryLayout;
109
+ abortSignalSAB: SharedArrayBuffer | import("../types.js").SharedBufferRegion;
110
+ abortSignalMax: number;
111
+ };
112
+ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug, hostDebug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, abortSignalCapacity, usesAbortSignal, sharedChannelHandler, stealPool, }: {
10
113
  list: string[];
11
114
  ids: number[];
12
115
  names: string[];
@@ -30,6 +133,26 @@ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug
30
133
  * macro channel that runs all lane checks.
31
134
  */
32
135
  sharedChannelHandler?: ChannelHandler;
136
+ /**
137
+ * Work-stealing wiring. When present the submit region, both host-side locks
138
+ * and the pending registry are owned by `createPool` and shared: every worker
139
+ * claims from one request region, and the host demultiplexes responses by
140
+ * task id. This lane only contributes its private return region and its
141
+ * worker. The dispatcher is owned by the pool too, so none is built here.
142
+ */
143
+ stealPool?: {
144
+ submitBuffers: LockBuffers;
145
+ returnBuffers: LockBuffers;
146
+ hostSubmitLock: ReturnType<typeof lock2>;
147
+ hostReturnLock: ReturnType<typeof lock2>;
148
+ sharedQueue: ReturnType<typeof createHostTxQueue>;
149
+ consumers: number;
150
+ consumerId: number;
151
+ regionLanes: number;
152
+ abortSignalSAB?: LockBuffers["headers"];
153
+ abortSignalMax?: number;
154
+ processMemory?: ProcessStealMemoryLayout;
155
+ };
33
156
  }) => (WorkerContext & {
34
157
  dispatcherCheck?: never;
35
158
  laneWake?: never;