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,6 @@
1
1
  import { createHostTxQueue } from "./tx-queue.js";
2
- import { cleanupProcessWorkerMemoryQuietly, createProcessSharedMemoryAllocator, createProcessWorkerMemoryLayout, createProcessWorkerNativeSignalNotifier, readProcessSharedMemorySettings, readProcessWorkerCommandPrefix, readProcessWorkerRuntime, serializeWorkerBootstrapData, spawnProcessWorker, terminateWorkerQuietly, toProcessWorkerBootPayload, toWorkerCompatExecArgv, toWorkerSafeExecArgv, } from "./process-worker.js";
2
+ import { cleanupProcessWorkerMemoryQuietly, createProcessSharedMemoryAllocator, createProcessStealMemoryLayout, createProcessWorkerMemoryLayout, createProcessWorkerNativeSignalNotifier, readProcessSharedMemorySettings, readProcessWorkerCommandPrefix, readProcessWorkerRuntime, spawnProcessWorker, toProcessWorkerBootPayload, } from "./process-worker.js";
3
+ import { serializeWorkerBootstrapData, terminateWorkerQuietly, toWorkerCompatExecArgv, toWorkerSafeExecArgv, } from "./worker-common.js";
3
4
  import { createSharedMemoryTransport, TRANSPORT_SIGNAL_BYTES, } from "../ipc/transport/shared-memory.js";
4
5
  import { ChannelHandler, hostDispatcherLoop, } from "./dispatcher.js";
5
6
  import { HEADER_SLOT_STRIDE_U32, lock2, LOCK_SECTOR_BYTE_LENGTH, LockBound, } from "../memory/lock.js";
@@ -7,7 +8,7 @@ import { HEADER_SLOT_STRIDE_U32, lock2, LOCK_SECTOR_BYTE_LENGTH, LockBound, } fr
7
8
  // see lock.ts). Must run before any lock2() call.
8
9
  import "../memory/payloadCodec.js";
9
10
  import "../worker/loop.js";
10
- import { createSharedArrayBuffer, createWasmSharedArrayBuffer, } from "../common/runtime.js";
11
+ import { createSharedArrayBuffer, createWasmSharedArrayBuffer, IS_BROWSER, RUNTIME, } from "../common/runtime.js";
11
12
  import { HAS_NODE_WORKER_THREADS, RUNTIME_WORKER, } from "../common/worker-runtime.js";
12
13
  import { probeLockBufferTextCompat } from "../common/shared-buffer-text.js";
13
14
  import { signalAbortFactory } from "../shared/abortSignal.js";
@@ -21,6 +22,15 @@ const isWorkerFatalMessage = (value) => !!value &&
21
22
  typeof value[WORKER_FATAL_MESSAGE_KEY] === "string";
22
23
  // Keep idle workers self-healing if an Atomics.notify wake is missed.
23
24
  const DEFAULT_WORKER_PARK_MS = 1;
25
+ const DEFAULT_ABORT_SIGNAL_CAPACITY = 258;
26
+ const sanitizePositiveInteger = (value) => {
27
+ if (!Number.isFinite(value))
28
+ return undefined;
29
+ const parsed = Math.floor(value);
30
+ return parsed > 0 ? parsed : undefined;
31
+ };
32
+ const resolveAbortSignalCapacity = (value) => sanitizePositiveInteger(value) ?? DEFAULT_ABORT_SIGNAL_CAPACITY;
33
+ const abortSignalByteLength = (capacity) => Math.max(1, Math.ceil(capacity / 32)) * Uint32Array.BYTES_PER_ELEMENT;
24
34
  const withDefaultWorkerTimers = (options) => {
25
35
  const parkMs = options?.timers?.parkMs ?? DEFAULT_WORKER_PARK_MS;
26
36
  if (options === undefined)
@@ -38,7 +48,135 @@ const withFixedPayloadConfig = (config) => ({
38
48
  mode: "fixed",
39
49
  payloadInitialBytes: config.payloadMaxByteLength,
40
50
  });
41
- export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostDebug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, abortSignalCapacity, usesAbortSignal, sharedChannelHandler, }) => {
51
+ /**
52
+ * Build the buffers, host-side locks and pending registry that a stealing pool
53
+ * shares. One submit region for every worker to claim from, one private return
54
+ * region per worker, and a single pool-global queue so a response arriving on
55
+ * any lane settles the right promise.
56
+ *
57
+ * Region width follows paper §6.1: the largest power-of-two `g` leaving a spare
58
+ * region for a delayed claimant (`slots / g >= consumers + 1`). At 32 slots that
59
+ * caps how wide a region can be well before it caps the worker count.
60
+ */
61
+ export const resolveStealRegionLanes = (consumers) => {
62
+ for (let lanes = LockBound.slots; lanes >= 1; lanes >>= 1) {
63
+ if (LockBound.slots / lanes >= consumers + 1)
64
+ return lanes;
65
+ }
66
+ throw new RangeError(`${consumers} stealing workers need more than ${LockBound.slots} lanes`);
67
+ };
68
+ /** Maximum claimants that leave the protocol's required spare region. */
69
+ export const MAX_STEAL_CONSUMERS = LockBound.slots - 1;
70
+ export const createStealPoolBuffers = ({ threads, payload, regionLanes, abortSignalCapacity, usesAbortSignal, processWorker, }) => {
71
+ const basePayloadConfig = resolvePayloadBufferOptions({ options: payload });
72
+ const payloadConfig = processWorker === undefined
73
+ ? basePayloadConfig
74
+ : withFixedPayloadConfig(basePayloadConfig);
75
+ const makePayload = () => payloadConfig.mode === "growable"
76
+ ? createSharedArrayBuffer(payloadConfig.payloadInitialBytes, payloadConfig.payloadMaxByteLength)
77
+ : createSharedArrayBuffer(payloadConfig.payloadInitialBytes);
78
+ const carpet = () => createLockControlCarpet({
79
+ signalBytes: 0,
80
+ abortBytes: 0,
81
+ lockSectorBytes: LOCK_SECTOR_BYTE_LENGTH,
82
+ headerSlotStrideU32: HEADER_SLOT_STRIDE_U32,
83
+ slotCount: LockBound.slots,
84
+ headerLayout: "split",
85
+ });
86
+ const toBuffers = (half, payloadSab) => ({
87
+ ...half,
88
+ payload: payloadSab,
89
+ textCompat: probeLockBufferTextCompat({
90
+ headers: half.headers,
91
+ payload: payloadSab,
92
+ }),
93
+ });
94
+ const maxLanes = resolveStealRegionLanes(threads);
95
+ const lanes = regionLanes === undefined
96
+ ? maxLanes
97
+ : Math.min(Math.max(1, regionLanes | 0), maxLanes);
98
+ const resolvedAbortSignalCapacity = resolveAbortSignalCapacity(abortSignalCapacity);
99
+ const processMemory = processWorker === undefined ? undefined : createProcessStealMemoryLayout({
100
+ threads,
101
+ signalBytes: processWorker.signalBytes,
102
+ abortBytes: usesAbortSignal === true
103
+ ? abortSignalByteLength(resolvedAbortSignalCapacity)
104
+ : 0,
105
+ abortSignalMax: usesAbortSignal === true
106
+ ? resolvedAbortSignalCapacity
107
+ : undefined,
108
+ payloadBytes: payloadConfig.payloadMaxByteLength,
109
+ sharedMemory: processWorker.sharedMemory,
110
+ });
111
+ const submitBuffers = processMemory === undefined
112
+ ? (() => {
113
+ const submitCarpet = carpet();
114
+ return toBuffers(submitCarpet.lock, makePayload());
115
+ })()
116
+ : toBuffers(processMemory.workers[0].controlLayout.lock, processMemory.workers[0].lockPayload);
117
+ const hostSubmitLock = lock2({
118
+ headers: submitBuffers.headers,
119
+ headerSlotStrideU32: submitBuffers.headerSlotStrideU32,
120
+ LockBoundSector: submitBuffers.lockSector,
121
+ payload: submitBuffers.payload,
122
+ payloadSector: submitBuffers.payloadSector,
123
+ payloadConfig,
124
+ textCompat: submitBuffers.textCompat,
125
+ consumers: threads,
126
+ regionLanes: lanes,
127
+ processBoundary: processMemory !== undefined,
128
+ });
129
+ const returnBuffers = [];
130
+ const hostReturnLocks = [];
131
+ for (let i = 0; i < threads; i++) {
132
+ const buffers = processMemory === undefined
133
+ ? toBuffers(carpet().returnLock, makePayload())
134
+ : toBuffers(processMemory.workers[i].controlLayout.returnLock, processMemory.workers[i].returnPayload);
135
+ returnBuffers.push(buffers);
136
+ hostReturnLocks.push(lock2({
137
+ headers: buffers.headers,
138
+ headerSlotStrideU32: buffers.headerSlotStrideU32,
139
+ LockBoundSector: buffers.lockSector,
140
+ payload: buffers.payload,
141
+ payloadSector: buffers.payloadSector,
142
+ payloadConfig,
143
+ textCompat: buffers.textCompat,
144
+ processBoundary: processMemory !== undefined,
145
+ }));
146
+ }
147
+ // Abort-aware tasks can execute on any claimant, so all workers and the
148
+ // pool-global queue must observe one bitmap. Per-lane abort buffers would
149
+ // encode an id from one allocator and test it against another lane's bits.
150
+ const abortSignalSAB = usesAbortSignal === true
151
+ ? processMemory?.workers[0]?.controlLayout.abortSignals ??
152
+ createSharedArrayBuffer(abortSignalByteLength(resolvedAbortSignalCapacity))
153
+ : undefined;
154
+ const abortSignals = abortSignalSAB === undefined
155
+ ? undefined
156
+ : signalAbortFactory({
157
+ sab: abortSignalSAB,
158
+ maxSignals: resolvedAbortSignalCapacity,
159
+ });
160
+ const sharedQueue = createHostTxQueue({
161
+ lock: hostSubmitLock,
162
+ returnLock: hostReturnLocks[0],
163
+ extraReturnLocks: hostReturnLocks.slice(1),
164
+ abortSignals,
165
+ });
166
+ return {
167
+ submitBuffers,
168
+ returnBuffers,
169
+ hostSubmitLock,
170
+ hostReturnLocks,
171
+ sharedQueue,
172
+ regionLanes: lanes,
173
+ processMemory,
174
+ abortSignalSAB,
175
+ abortSignalMax: processMemory?.abortSignalMax ??
176
+ (abortSignalSAB === undefined ? undefined : resolvedAbortSignalCapacity),
177
+ };
178
+ };
179
+ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostDebug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, abortSignalCapacity, usesAbortSignal, sharedChannelHandler, stealPool, }) => {
42
180
  if (workerOptions?.runtime === "compiled") {
43
181
  return spawnCompiledWorkerContext({
44
182
  list,
@@ -65,41 +203,51 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
65
203
  if (!useProcessWorkerRuntime && typeof poliWorker !== "function") {
66
204
  throw new Error("Worker is not available in this runtime");
67
205
  }
206
+ // Without this the failure is a `ReferenceError` from whichever module
207
+ // happens to allocate first.
208
+ if (IS_BROWSER && typeof SharedArrayBuffer !== "function") {
209
+ throw new Error("SharedArrayBuffer is unavailable: serve the page cross-origin isolated " +
210
+ "(Cross-Origin-Opener-Policy: same-origin, " +
211
+ "Cross-Origin-Embedder-Policy: require-corp).");
212
+ }
68
213
  const WorkerCtor = poliWorker;
69
214
  // Lock buffers must be shared between host and worker.
70
- const sanitizeBytes = (value) => {
71
- if (!Number.isFinite(value))
72
- return undefined;
73
- const bytes = Math.floor(value);
74
- return bytes > 0 ? bytes : undefined;
75
- };
215
+ const sanitizeBytes = sanitizePositiveInteger;
76
216
  const basePayloadConfig = resolvePayloadBufferOptions({
77
217
  options: payload,
78
218
  });
79
219
  const resolvedPayloadConfig = useProcessWorkerRuntime
80
220
  ? withFixedPayloadConfig(basePayloadConfig)
81
221
  : basePayloadConfig;
82
- const defaultAbortSignalCapacity = 258;
83
- const requestedAbortSignalCapacity = sanitizeBytes(abortSignalCapacity);
84
- const resolvedAbortSignalCapacity = requestedAbortSignalCapacity ??
85
- defaultAbortSignalCapacity;
86
- const abortSignalWords = Math.max(1, Math.ceil(resolvedAbortSignalCapacity / 32));
222
+ const resolvedAbortSignalCapacity = resolveAbortSignalCapacity(abortSignalCapacity);
87
223
  const requestedSignalBytes = sanitizeBytes(sab?.size);
88
224
  const externalSignalSab = sab?.sharedSab;
89
225
  if (useProcessWorkerRuntime && externalSignalSab != null) {
90
226
  throw new Error("process worker runtime cannot use an external signal buffer");
91
227
  }
92
228
  const signalBytes = Math.max(TRANSPORT_SIGNAL_BYTES, requestedSignalBytes ?? TRANSPORT_SIGNAL_BYTES);
93
- const abortBytes = abortSignalWords * Uint32Array.BYTES_PER_ELEMENT;
94
- const processWorkerMemory = useProcessWorkerRuntime
95
- ? createProcessWorkerMemoryLayout({
96
- signalBytes,
97
- abortBytes,
98
- payloadBytes: resolvedPayloadConfig.payloadMaxByteLength,
99
- thread,
100
- sharedMemory: processSharedMemorySettings,
101
- })
102
- : undefined;
229
+ const abortBytes = stealPool === undefined && usesAbortSignal === true
230
+ ? abortSignalByteLength(resolvedAbortSignalCapacity)
231
+ : 0;
232
+ // A stealing process pool carves every lane out of one mapping, so lane
233
+ // `thread` must find its slice there. Falling back to a private layout would
234
+ // silently give this lane its own submit region — it would boot and then
235
+ // never see a task the host published to the shared one.
236
+ const stealProcessMemory = stealPool?.processMemory;
237
+ const processWorkerMemory = !useProcessWorkerRuntime
238
+ ? undefined
239
+ : stealProcessMemory === undefined
240
+ ? createProcessWorkerMemoryLayout({
241
+ signalBytes,
242
+ abortBytes,
243
+ payloadBytes: resolvedPayloadConfig.payloadMaxByteLength,
244
+ thread,
245
+ sharedMemory: processSharedMemorySettings,
246
+ })
247
+ : stealProcessMemory.workers[thread] ??
248
+ (() => {
249
+ throw new RangeError(`stealing process pool has no shared-memory slice for worker ${thread}`);
250
+ })();
103
251
  const processSharedMemory = processWorkerMemory === undefined
104
252
  ? createProcessSharedMemoryAllocator(debug)
105
253
  : undefined;
@@ -132,7 +280,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
132
280
  const controlLayout = processWorkerMemory?.controlLayout ??
133
281
  makeLockControlLayout();
134
282
  const lockPayload = processWorkerMemory?.lockPayload ?? makePayloadBuffer();
135
- const lockBuffers = {
283
+ const lockBuffers = stealPool?.submitBuffers ?? {
136
284
  ...controlLayout.lock,
137
285
  payload: lockPayload,
138
286
  textCompat: probeLockBufferTextCompat({
@@ -142,7 +290,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
142
290
  };
143
291
  const returnPayload = processWorkerMemory?.returnPayload ??
144
292
  makePayloadBuffer();
145
- const returnLockBuffers = {
293
+ const returnLockBuffers = stealPool?.returnBuffers ?? {
146
294
  ...controlLayout.returnLock,
147
295
  payload: returnPayload,
148
296
  textCompat: probeLockBufferTextCompat({
@@ -150,7 +298,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
150
298
  payload: returnPayload,
151
299
  }),
152
300
  };
153
- const lock = lock2({
301
+ const lock = stealPool?.hostSubmitLock ?? lock2({
154
302
  headers: lockBuffers.headers,
155
303
  headerSlotStrideU32: lockBuffers.headerSlotStrideU32,
156
304
  LockBoundSector: lockBuffers.lockSector,
@@ -160,7 +308,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
160
308
  textCompat: lockBuffers.textCompat,
161
309
  processBoundary: useProcessWorkerRuntime,
162
310
  });
163
- const returnLock = lock2({
311
+ const returnLock = stealPool?.hostReturnLock ?? lock2({
164
312
  headers: returnLockBuffers.headers,
165
313
  headerSlotStrideU32: returnLockBuffers.headerSlotStrideU32,
166
314
  LockBoundSector: returnLockBuffers.lockSector,
@@ -170,10 +318,9 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
170
318
  textCompat: returnLockBuffers.textCompat,
171
319
  processBoundary: useProcessWorkerRuntime,
172
320
  });
173
- const abortSignalSAB = usesAbortSignal === true
174
- ? controlLayout.abortSignals
175
- : undefined;
176
- const abortSignals = abortSignalSAB
321
+ const abortSignalSAB = stealPool?.abortSignalSAB ??
322
+ (usesAbortSignal === true ? controlLayout.abortSignals : undefined);
323
+ const abortSignals = abortSignalSAB && stealPool === undefined
177
324
  ? signalAbortFactory({
178
325
  sab: abortSignalSAB,
179
326
  maxSignals: resolvedAbortSignalCapacity,
@@ -224,34 +371,38 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
224
371
  }
225
372
  outstandingBorrowedReturns.clear();
226
373
  };
227
- const queue = createHostTxQueue({
374
+ const returnHooks = bufferReferenceReturn === "borrow"
375
+ ? {
376
+ release: releaseBorrowedReturnToken,
377
+ track: (ref, token, aliasBuffer) => {
378
+ if (outstandingBorrowedReturns === undefined)
379
+ return;
380
+ const record = outstandingBorrowedReturns.get(token);
381
+ if (record !== undefined) {
382
+ if (aliasBuffer !== undefined) {
383
+ record.aliasBuffer = new WeakRef(aliasBuffer);
384
+ }
385
+ return;
386
+ }
387
+ outstandingBorrowedReturns.set(token, {
388
+ ref: new WeakRef(ref),
389
+ aliasBuffer: aliasBuffer === undefined
390
+ ? undefined
391
+ : new WeakRef(aliasBuffer),
392
+ runtime: ref.runtime,
393
+ });
394
+ },
395
+ }
396
+ : undefined;
397
+ const queue = stealPool?.sharedQueue ?? createHostTxQueue({
228
398
  lock,
229
399
  returnLock,
230
400
  abortSignals,
231
- releaseBufferReferenceReturn: bufferReferenceReturn === "borrow"
232
- ? {
233
- release: releaseBorrowedReturnToken,
234
- track: (ref, token, aliasBuffer) => {
235
- if (outstandingBorrowedReturns === undefined)
236
- return;
237
- const record = outstandingBorrowedReturns.get(token);
238
- if (record !== undefined) {
239
- if (aliasBuffer !== undefined) {
240
- record.aliasBuffer = new WeakRef(aliasBuffer);
241
- }
242
- return;
243
- }
244
- outstandingBorrowedReturns.set(token, {
245
- ref: new WeakRef(ref),
246
- aliasBuffer: aliasBuffer === undefined
247
- ? undefined
248
- : new WeakRef(aliasBuffer),
249
- runtime: ref.runtime,
250
- });
251
- },
252
- }
253
- : undefined,
401
+ releaseBufferReferenceReturn: returnHooks,
254
402
  });
403
+ if (stealPool !== undefined) {
404
+ stealPool.sharedQueue.setReturnHooks(stealPool.consumerId, returnHooks);
405
+ }
255
406
  const { enqueue, rejectAll, txIdle, } = queue;
256
407
  const thisSignal = signalBox.opView;
257
408
  const a_add = Atomics.add;
@@ -271,16 +422,22 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
271
422
  let dispatchSend = () => { };
272
423
  const send = () => dispatchSend();
273
424
  let channelHandler;
274
- const ownsChannel = sharedChannelHandler === undefined;
425
+ const ownsChannel = sharedChannelHandler === undefined &&
426
+ stealPool === undefined;
275
427
  const ownChannel = sharedChannelHandler ?? new ChannelHandler();
276
- const { check: dispatcherCheck } = hostDispatcherLoop({
277
- signalBox,
278
- queue,
279
- channelHandler: ownChannel,
280
- dispatcherOptions: host,
281
- notifySignal: nativeNotifySignal,
282
- });
283
- if (ownsChannel) {
428
+ // Under stealing there is one queue, so one dispatcher drives it from the
429
+ // pool; a per-lane dispatcher would double-drain the shared registry.
430
+ const { check: dispatcherCheck } = stealPool !== undefined
431
+ ? { check: undefined }
432
+ : hostDispatcherLoop({
433
+ signalBox,
434
+ queue,
435
+ channelHandler: ownChannel,
436
+ dispatcherOptions: host,
437
+ notifySignal: nativeNotifySignal,
438
+ crossProcess: useProcessWorkerRuntime,
439
+ });
440
+ if (ownsChannel && dispatcherCheck !== undefined) {
284
441
  ownChannel.open(dispatcherCheck);
285
442
  channelHandler = ownChannel;
286
443
  dispatchSend = () => {
@@ -304,9 +461,8 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
304
461
  const workerDataPayload = {
305
462
  sab: signals.sab,
306
463
  abortSignalSAB,
307
- abortSignalMax: usesAbortSignal === true
308
- ? resolvedAbortSignalCapacity
309
- : undefined,
464
+ abortSignalMax: stealPool?.abortSignalMax ??
465
+ (usesAbortSignal === true ? resolvedAbortSignalCapacity : undefined),
310
466
  list,
311
467
  ids,
312
468
  names,
@@ -321,6 +477,17 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
321
477
  payloadConfig: resolvedPayloadConfig,
322
478
  bufferReferenceReturn,
323
479
  permission,
480
+ // Mirrors the dispatcher's `canUseDoorbell`: a cross-process worker cannot
481
+ // ring the host's waiter, so do not make it pay the atomic op.
482
+ notifyOnHostPublish: !useProcessWorkerRuntime &&
483
+ host?.doorbell !== false &&
484
+ (RUNTIME === "bun" || RUNTIME === "node") &&
485
+ typeof Atomics.waitAsync === "function",
486
+ steal: stealPool === undefined ? undefined : {
487
+ consumers: stealPool.consumers,
488
+ consumerId: stealPool.consumerId,
489
+ regionLanes: stealPool.regionLanes,
490
+ },
324
491
  };
325
492
  const baseWorkerOptions = {
326
493
  //@ts-ignore Reason
@@ -389,6 +556,20 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
389
556
  worker.postMessage?.(workerDataPayload);
390
557
  }
391
558
  let closedReason;
559
+ const deactivateStealConsumer = () => {
560
+ stealPool?.hostSubmitLock.deactivateStealConsumer(stealPool.consumerId);
561
+ };
562
+ const terminateFailedWorker = () => {
563
+ try {
564
+ worker.unref?.();
565
+ void Promise.resolve(worker.terminate())
566
+ .catch(() => { })
567
+ .finally(deactivateStealConsumer);
568
+ }
569
+ catch {
570
+ deactivateStealConsumer();
571
+ }
572
+ };
392
573
  // `workerBytesReadable`: while the worker is still alive its pinned bytes
393
574
  // can be copied out; after a crash/exit they are gone and borrows are
394
575
  // detached so stale reads fail loud instead of aliasing freed memory.
@@ -404,18 +585,22 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
404
585
  if (!isWorkerFatalMessage(message))
405
586
  return;
406
587
  markWorkerClosed(`Worker startup failed: ${message[WORKER_FATAL_MESSAGE_KEY]}`, true);
407
- terminateWorkerQuietly(worker);
588
+ terminateFailedWorker();
408
589
  };
409
590
  const onWorkerError = (error) => {
410
591
  const message = String(error?.message ?? error);
411
592
  markWorkerClosed(`Worker crashed: ${message}`);
593
+ terminateFailedWorker();
412
594
  };
413
595
  const nodeWorker = worker;
414
596
  if (typeof nodeWorker.on === "function") {
415
597
  nodeWorker.on("message", onWorkerMessage);
416
598
  nodeWorker.on("error", onWorkerError);
417
599
  nodeWorker.on("exit", (code) => {
418
- if (typeof code === "number" && code === 0)
600
+ // Exit is the point at which this endpoint can no longer write WANT, so
601
+ // survivors may safely ignore any intent it left behind.
602
+ deactivateStealConsumer();
603
+ if (closedReason !== undefined)
419
604
  return;
420
605
  const normalized = typeof code === "number" ? code : -1;
421
606
  markWorkerClosed(`Worker exited with code ${normalized}`);
@@ -457,14 +642,18 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
457
642
  call,
458
643
  kills: async () => {
459
644
  markWorkerClosed("Thread closed", true);
460
- terminateWorkerQuietly(worker);
645
+ const termination = terminateWorkerQuietly(worker);
646
+ if (processWorkerMemory !== undefined)
647
+ await termination;
461
648
  cleanupProcessWorkerMemoryQuietly(processWorkerMemory);
462
649
  },
463
650
  lock,
464
651
  processSharedMemoryBackings: processSharedMemory?.backings,
465
652
  dispatcherCheck,
466
- laneWake: sharedChannelHandler !== undefined ? laneWake : undefined,
467
- bindSend: sharedChannelHandler !== undefined
653
+ laneWake: sharedChannelHandler !== undefined || stealPool !== undefined
654
+ ? laneWake
655
+ : undefined,
656
+ bindSend: sharedChannelHandler !== undefined || stealPool !== undefined
468
657
  ? ((fn) => void (dispatchSend = fn))
469
658
  : undefined,
470
659
  };
@@ -1,17 +1,10 @@
1
1
  import { type LockControlCarpet } from "../memory/byte-carpet.js";
2
2
  import type { DebugOptions, LockBuffers, WorkerData, WorkerSettings } from "../types.js";
3
+ import { type NodeWorkerLike, type SpawnedWorker } from "./worker-common.js";
3
4
  import { RUNTIME_PROCESS_WORKER_BOOT_VERSION } from "../common/worker-runtime.js";
4
5
  import { type SharedBufferSource } from "../common/shared-buffer-region.js";
5
6
  import { FileDescriptor, type ProcessSharedBufferMetadata } from "../connections/index.js";
6
7
  import type { SharedMemoryBuffer, SharedMemoryMapping } from "../connections/types.js";
7
- export type SpawnedWorker = {
8
- terminate: () => unknown;
9
- unref?: () => unknown;
10
- postMessage?: (message: unknown) => void;
11
- };
12
- export type NodeWorkerLike = {
13
- on?: (event: "error" | "exit" | "message", listener: (...args: unknown[]) => void) => void;
14
- };
15
8
  type ProcessWorkerWireLockBuffers = Omit<LockBuffers, "headers" | "lockSector" | "payload" | "payloadSector"> & {
16
9
  headers: ProcessSharedBufferMetadata;
17
10
  lockSector: ProcessSharedBufferMetadata;
@@ -36,6 +29,20 @@ export type ProcessWorkerMemoryLayout = {
36
29
  returnPayload: SharedBufferSource;
37
30
  cleanup: () => void;
38
31
  };
32
+ /**
33
+ * Process-worker memory for a stealing pool.
34
+ *
35
+ * The request side is one shared region all child processes may claim from;
36
+ * each worker keeps its own signals and return region. Every per-worker view
37
+ * points into the same OS mapping so the existing fd/name boot protocol can
38
+ * carry all of the slices without adding a second descriptor to WorkerData.
39
+ */
40
+ export type ProcessStealMemoryLayout = {
41
+ mapping: SharedMemoryMapping<SharedMemoryBuffer>;
42
+ descriptor: FileDescriptor;
43
+ workers: ProcessWorkerMemoryLayout[];
44
+ abortSignalMax?: number;
45
+ };
39
46
  export type ProcessWorkerRuntime = "bun" | "deno" | "node";
40
47
  export type ProcessWorkerCommandPrefix = NonNullable<WorkerSettings["processCommandPrefix"]>;
41
48
  export type ResolvedProcessSharedMemorySettings = {
@@ -60,8 +67,6 @@ type ProcessSharedMemoryAllocator = {
60
67
  createBuffer: (byteLength: number) => SharedArrayBuffer;
61
68
  backings: ProcessSharedMemoryBacking[];
62
69
  };
63
- export declare const toWorkerSafeExecArgv: (flags: string[] | undefined) => string[] | undefined;
64
- export declare const toWorkerCompatExecArgv: (flags: string[] | undefined) => string[] | undefined;
65
70
  export declare const createProcessSharedMemoryAllocator: (debug: DebugOptions | undefined) => ProcessSharedMemoryAllocator | undefined;
66
71
  export declare const createProcessWorkerMemoryLayout: ({ signalBytes, abortBytes, payloadBytes, thread, sharedMemory, }: {
67
72
  signalBytes: number;
@@ -70,11 +75,19 @@ export declare const createProcessWorkerMemoryLayout: ({ signalBytes, abortBytes
70
75
  thread: number;
71
76
  sharedMemory: ResolvedProcessSharedMemorySettings;
72
77
  }) => ProcessWorkerMemoryLayout;
73
- export declare const serializeWorkerBootstrapData: (options: WorkerSettings) => WorkerSettings;
78
+ export declare const createProcessStealMemoryLayout: ({ threads, signalBytes, abortBytes, abortSignalMax, payloadBytes, sharedMemory, }: {
79
+ threads: number;
80
+ signalBytes: number;
81
+ abortBytes: number;
82
+ abortSignalMax?: number;
83
+ payloadBytes: number;
84
+ sharedMemory: ResolvedProcessSharedMemorySettings;
85
+ }) => ProcessStealMemoryLayout;
74
86
  export declare const toProcessWorkerBootPayload: (workerData: WorkerData, memory: ProcessWorkerMemoryLayout) => ProcessWorkerBootPayload;
75
87
  export declare const readProcessWorkerRuntime: (options: WorkerSettings | undefined) => ProcessWorkerRuntime;
76
88
  export declare const readProcessWorkerCommandPrefix: (options: WorkerSettings | undefined) => ProcessWorkerCommandPrefix | undefined;
77
89
  export declare const readProcessSharedMemorySettings: (options: WorkerSettings | undefined) => ResolvedProcessSharedMemorySettings;
90
+ export declare const readProcessWorkerNodeMajor: (options: WorkerSettings | undefined) => number | undefined;
78
91
  export declare const createProcessWorkerNativeSignalNotifier: ({ processRuntime, signal, }: {
79
92
  processRuntime: ProcessWorkerRuntime | undefined;
80
93
  signal: Int32Array;
@@ -87,6 +100,5 @@ export declare const spawnProcessWorker: (options: {
87
100
  commandPrefix?: ProcessWorkerCommandPrefix;
88
101
  permission?: WorkerData["permission"];
89
102
  }) => SpawnedWorker & NodeWorkerLike;
90
- export declare const terminateWorkerQuietly: (worker: SpawnedWorker) => void;
91
103
  export declare const cleanupProcessWorkerMemoryQuietly: (memory: ProcessWorkerMemoryLayout | undefined) => void;
92
104
  export {};