knitting 0.1.56 → 0.1.61

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 (101) hide show
  1. package/BROWSER.md +134 -0
  2. package/README.md +347 -46
  3. package/knitting.browser.d.ts +6 -0
  4. package/knitting.browser.js +1 -0
  5. package/knitting.d.ts +3 -2
  6. package/knitting.js +3 -3
  7. package/map.md +21 -9
  8. package/package.json +17 -2
  9. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  10. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  11. package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
  12. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  13. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  14. package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
  15. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  16. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  17. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  18. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  19. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  20. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  21. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  22. package/scripts/build-compiled-worker.ts +276 -0
  23. package/scripts/compiled-worker/porffor.d.ts +5 -0
  24. package/scripts/compiled-worker/runtime.ts +529 -0
  25. package/scripts/compiled-worker/task-shim.ts +32 -0
  26. package/src/api.d.ts +24 -1
  27. package/src/api.js +311 -27
  28. package/src/common/module-url.d.ts +1 -0
  29. package/src/common/module-url.js +30 -2
  30. package/src/common/node-compat.d.ts +2 -0
  31. package/src/common/path-canonical.js +20 -6
  32. package/src/common/runtime.d.ts +3 -1
  33. package/src/common/runtime.js +25 -4
  34. package/src/common/task-source.d.ts +2 -0
  35. package/src/common/task-source.js +17 -1
  36. package/src/common/worker-runtime.js +16 -7
  37. package/src/connections/buffer-reference-native.js +25 -1
  38. package/src/connections/buffer-reference.d.ts +23 -2
  39. package/src/connections/buffer-reference.js +143 -29
  40. package/src/connections/bun.d.ts +1 -1
  41. package/src/connections/bun.js +25 -14
  42. package/src/connections/deno.d.ts +3 -1
  43. package/src/connections/deno.js +35 -14
  44. package/src/connections/external-array-buffer.d.ts +2 -0
  45. package/src/connections/external-array-buffer.js +33 -0
  46. package/src/connections/node-addons.d.ts +11 -0
  47. package/src/connections/node-addons.js +49 -1
  48. package/src/connections/node-ffi-api.d.ts +19 -0
  49. package/src/connections/node-ffi-api.js +28 -0
  50. package/src/connections/node-ffi.d.ts +27 -0
  51. package/src/connections/node-ffi.js +262 -0
  52. package/src/connections/node.d.ts +1 -1
  53. package/src/connections/node.js +20 -20
  54. package/src/connections/package-assets.js +25 -15
  55. package/src/connections/posix.d.ts +42 -0
  56. package/src/connections/posix.js +101 -0
  57. package/src/connections/process-shared-buffer.d.ts +1 -0
  58. package/src/connections/process-shared-buffer.js +11 -45
  59. package/src/connections/types.d.ts +4 -0
  60. package/src/connections/windows.d.ts +19 -10
  61. package/src/connections/windows.js +87 -66
  62. package/src/error.d.ts +7 -6
  63. package/src/error.js +8 -7
  64. package/src/memory/lock.d.ts +112 -77
  65. package/src/memory/lock.js +314 -87
  66. package/src/memory/payloadCodec.d.ts +11 -2
  67. package/src/memory/payloadCodec.js +129 -37
  68. package/src/memory/regionRegistry.d.ts +1 -3
  69. package/src/memory/regionRegistry.js +16 -14
  70. package/src/permission/compatibility.d.ts +28 -0
  71. package/src/permission/compatibility.js +239 -0
  72. package/src/permission/index.d.ts +2 -0
  73. package/src/permission/index.js +1 -0
  74. package/src/permission/protocol.d.ts +1 -0
  75. package/src/permission/protocol.js +59 -15
  76. package/src/runtime/compiled-artifact.d.ts +21 -0
  77. package/src/runtime/compiled-artifact.js +260 -0
  78. package/src/runtime/compiled-builder.d.ts +8 -0
  79. package/src/runtime/compiled-builder.js +42 -0
  80. package/src/runtime/compiled-worker.d.ts +27 -0
  81. package/src/runtime/compiled-worker.js +562 -0
  82. package/src/runtime/dispatcher.js +9 -0
  83. package/src/runtime/inline-executor.js +27 -15
  84. package/src/runtime/pool.d.ts +121 -5
  85. package/src/runtime/pool.js +263 -49
  86. package/src/runtime/process-worker.d.ts +3 -13
  87. package/src/runtime/process-worker.js +97 -101
  88. package/src/runtime/tx-queue.d.ts +18 -2
  89. package/src/runtime/tx-queue.js +56 -16
  90. package/src/runtime/worker-common.d.ts +13 -0
  91. package/src/runtime/worker-common.js +102 -0
  92. package/src/types.d.ts +89 -9
  93. package/src/worker/composable-runners.js +3 -2
  94. package/src/worker/loop.d.ts +1 -0
  95. package/src/worker/loop.js +52 -17
  96. package/src/worker/rx-queue.d.ts +11 -1
  97. package/src/worker/rx-queue.js +4 -1
  98. package/src/worker/task-loader.d.ts +5 -4
  99. package/src/worker/task-loader.js +13 -7
  100. package/src/worker/timers.d.ts +8 -1
  101. package/src/worker/timers.js +28 -23
@@ -1,22 +1,36 @@
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, 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
+ // Side-effect import: registers the payload codec (cycle break for Andromeda;
8
+ // see lock.ts). Must run before any lock2() call.
9
+ import "../memory/payloadCodec.js";
6
10
  import "../worker/loop.js";
7
- import { createSharedArrayBuffer, createWasmSharedArrayBuffer, } from "../common/runtime.js";
11
+ import { createSharedArrayBuffer, createWasmSharedArrayBuffer, IS_BROWSER, } from "../common/runtime.js";
8
12
  import { HAS_NODE_WORKER_THREADS, RUNTIME_WORKER, } from "../common/worker-runtime.js";
9
13
  import { probeLockBufferTextCompat } from "../common/shared-buffer-text.js";
10
14
  import { signalAbortFactory } from "../shared/abortSignal.js";
11
15
  import { createLockControlCarpet } from "../memory/byte-carpet.js";
12
16
  import { resolvePayloadBufferOptions, } from "../memory/payload-config.js";
13
- import { createBufferReferenceReturnReleaseMessage } from "../connections/buffer-reference.js";
17
+ import { createBufferReferenceReturnReleaseMessage, detachArrayBufferBestEffort, } from "../connections/buffer-reference.js";
18
+ import { spawnCompiledWorkerContext } from "./compiled-worker.js";
14
19
  const WORKER_FATAL_MESSAGE_KEY = "__knittingWorkerFatal";
15
20
  const isWorkerFatalMessage = (value) => !!value &&
16
21
  typeof value === "object" &&
17
22
  typeof value[WORKER_FATAL_MESSAGE_KEY] === "string";
18
23
  // Keep idle workers self-healing if an Atomics.notify wake is missed.
19
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;
20
34
  const withDefaultWorkerTimers = (options) => {
21
35
  const parkMs = options?.timers?.parkMs ?? DEFAULT_WORKER_PARK_MS;
22
36
  if (options === undefined)
@@ -34,7 +48,121 @@ const withFixedPayloadConfig = (config) => ({
34
48
  mode: "fixed",
35
49
  payloadInitialBytes: config.payloadMaxByteLength,
36
50
  });
37
- 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, }) => {
71
+ const payloadConfig = resolvePayloadBufferOptions({ options: payload });
72
+ const makePayload = () => payloadConfig.mode === "growable"
73
+ ? createSharedArrayBuffer(payloadConfig.payloadInitialBytes, payloadConfig.payloadMaxByteLength)
74
+ : createSharedArrayBuffer(payloadConfig.payloadInitialBytes);
75
+ const carpet = () => createLockControlCarpet({
76
+ signalBytes: 0,
77
+ abortBytes: 0,
78
+ lockSectorBytes: LOCK_SECTOR_BYTE_LENGTH,
79
+ headerSlotStrideU32: HEADER_SLOT_STRIDE_U32,
80
+ slotCount: LockBound.slots,
81
+ headerLayout: "split",
82
+ });
83
+ const toBuffers = (half, payloadSab) => ({
84
+ ...half,
85
+ payload: payloadSab,
86
+ textCompat: probeLockBufferTextCompat({
87
+ headers: half.headers,
88
+ payload: payloadSab,
89
+ }),
90
+ });
91
+ const maxLanes = resolveStealRegionLanes(threads);
92
+ const lanes = regionLanes === undefined
93
+ ? maxLanes
94
+ : Math.min(Math.max(1, regionLanes | 0), maxLanes);
95
+ const submitCarpet = carpet();
96
+ const submitBuffers = toBuffers(submitCarpet.lock, makePayload());
97
+ const hostSubmitLock = lock2({
98
+ headers: submitBuffers.headers,
99
+ headerSlotStrideU32: submitBuffers.headerSlotStrideU32,
100
+ LockBoundSector: submitBuffers.lockSector,
101
+ payload: submitBuffers.payload,
102
+ payloadSector: submitBuffers.payloadSector,
103
+ payloadConfig,
104
+ textCompat: submitBuffers.textCompat,
105
+ consumers: threads,
106
+ regionLanes: lanes,
107
+ });
108
+ const returnBuffers = [];
109
+ const hostReturnLocks = [];
110
+ for (let i = 0; i < threads; i++) {
111
+ const buffers = toBuffers(carpet().returnLock, makePayload());
112
+ returnBuffers.push(buffers);
113
+ hostReturnLocks.push(lock2({
114
+ headers: buffers.headers,
115
+ headerSlotStrideU32: buffers.headerSlotStrideU32,
116
+ LockBoundSector: buffers.lockSector,
117
+ payload: buffers.payload,
118
+ payloadSector: buffers.payloadSector,
119
+ payloadConfig,
120
+ textCompat: buffers.textCompat,
121
+ }));
122
+ }
123
+ // Abort-aware tasks can execute on any claimant, so all workers and the
124
+ // pool-global queue must observe one bitmap. Per-lane abort buffers would
125
+ // encode an id from one allocator and test it against another lane's bits.
126
+ const resolvedAbortSignalCapacity = resolveAbortSignalCapacity(abortSignalCapacity);
127
+ const abortSignalSAB = usesAbortSignal === true
128
+ ? createSharedArrayBuffer(abortSignalByteLength(resolvedAbortSignalCapacity))
129
+ : undefined;
130
+ const abortSignals = abortSignalSAB === undefined
131
+ ? undefined
132
+ : signalAbortFactory({
133
+ sab: abortSignalSAB,
134
+ maxSignals: resolvedAbortSignalCapacity,
135
+ });
136
+ const sharedQueue = createHostTxQueue({
137
+ lock: hostSubmitLock,
138
+ returnLock: hostReturnLocks[0],
139
+ extraReturnLocks: hostReturnLocks.slice(1),
140
+ abortSignals,
141
+ });
142
+ return {
143
+ submitBuffers,
144
+ returnBuffers,
145
+ hostSubmitLock,
146
+ hostReturnLocks,
147
+ sharedQueue,
148
+ regionLanes: lanes,
149
+ abortSignalSAB,
150
+ abortSignalMax: abortSignalSAB === undefined
151
+ ? undefined
152
+ : resolvedAbortSignalCapacity,
153
+ };
154
+ };
155
+ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostDebug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, abortSignalCapacity, usesAbortSignal, sharedChannelHandler, stealPool, }) => {
156
+ if (workerOptions?.runtime === "compiled") {
157
+ return spawnCompiledWorkerContext({
158
+ list,
159
+ names,
160
+ workerOptions,
161
+ hostDebug,
162
+ abortSignalCapacity,
163
+ usesAbortSignal,
164
+ });
165
+ }
38
166
  const tsFileUrl = new URL(import.meta.url);
39
167
  const poliWorker = RUNTIME_WORKER;
40
168
  const resolvedWorkerOptions = serializeWorkerBootstrapData(withDefaultWorkerTimers(workerOptions));
@@ -51,32 +179,32 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
51
179
  if (!useProcessWorkerRuntime && typeof poliWorker !== "function") {
52
180
  throw new Error("Worker is not available in this runtime");
53
181
  }
182
+ // Without this the failure is a `ReferenceError` from whichever module
183
+ // happens to allocate first.
184
+ if (IS_BROWSER && typeof SharedArrayBuffer !== "function") {
185
+ throw new Error("SharedArrayBuffer is unavailable: serve the page cross-origin isolated " +
186
+ "(Cross-Origin-Opener-Policy: same-origin, " +
187
+ "Cross-Origin-Embedder-Policy: require-corp).");
188
+ }
54
189
  const WorkerCtor = poliWorker;
55
190
  // Lock buffers must be shared between host and worker.
56
- const sanitizeBytes = (value) => {
57
- if (!Number.isFinite(value))
58
- return undefined;
59
- const bytes = Math.floor(value);
60
- return bytes > 0 ? bytes : undefined;
61
- };
191
+ const sanitizeBytes = sanitizePositiveInteger;
62
192
  const basePayloadConfig = resolvePayloadBufferOptions({
63
193
  options: payload,
64
194
  });
65
195
  const resolvedPayloadConfig = useProcessWorkerRuntime
66
196
  ? withFixedPayloadConfig(basePayloadConfig)
67
197
  : basePayloadConfig;
68
- const defaultAbortSignalCapacity = 258;
69
- const requestedAbortSignalCapacity = sanitizeBytes(abortSignalCapacity);
70
- const resolvedAbortSignalCapacity = requestedAbortSignalCapacity ??
71
- defaultAbortSignalCapacity;
72
- const abortSignalWords = Math.max(1, Math.ceil(resolvedAbortSignalCapacity / 32));
198
+ const resolvedAbortSignalCapacity = resolveAbortSignalCapacity(abortSignalCapacity);
73
199
  const requestedSignalBytes = sanitizeBytes(sab?.size);
74
200
  const externalSignalSab = sab?.sharedSab;
75
201
  if (useProcessWorkerRuntime && externalSignalSab != null) {
76
202
  throw new Error("process worker runtime cannot use an external signal buffer");
77
203
  }
78
204
  const signalBytes = Math.max(TRANSPORT_SIGNAL_BYTES, requestedSignalBytes ?? TRANSPORT_SIGNAL_BYTES);
79
- const abortBytes = abortSignalWords * Uint32Array.BYTES_PER_ELEMENT;
205
+ const abortBytes = stealPool === undefined && usesAbortSignal === true
206
+ ? abortSignalByteLength(resolvedAbortSignalCapacity)
207
+ : 0;
80
208
  const processWorkerMemory = useProcessWorkerRuntime
81
209
  ? createProcessWorkerMemoryLayout({
82
210
  signalBytes,
@@ -118,7 +246,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
118
246
  const controlLayout = processWorkerMemory?.controlLayout ??
119
247
  makeLockControlLayout();
120
248
  const lockPayload = processWorkerMemory?.lockPayload ?? makePayloadBuffer();
121
- const lockBuffers = {
249
+ const lockBuffers = stealPool?.submitBuffers ?? {
122
250
  ...controlLayout.lock,
123
251
  payload: lockPayload,
124
252
  textCompat: probeLockBufferTextCompat({
@@ -128,7 +256,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
128
256
  };
129
257
  const returnPayload = processWorkerMemory?.returnPayload ??
130
258
  makePayloadBuffer();
131
- const returnLockBuffers = {
259
+ const returnLockBuffers = stealPool?.returnBuffers ?? {
132
260
  ...controlLayout.returnLock,
133
261
  payload: returnPayload,
134
262
  textCompat: probeLockBufferTextCompat({
@@ -136,7 +264,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
136
264
  payload: returnPayload,
137
265
  }),
138
266
  };
139
- const lock = lock2({
267
+ const lock = stealPool?.hostSubmitLock ?? lock2({
140
268
  headers: lockBuffers.headers,
141
269
  headerSlotStrideU32: lockBuffers.headerSlotStrideU32,
142
270
  LockBoundSector: lockBuffers.lockSector,
@@ -144,8 +272,9 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
144
272
  payloadSector: lockBuffers.payloadSector,
145
273
  payloadConfig: resolvedPayloadConfig,
146
274
  textCompat: lockBuffers.textCompat,
275
+ processBoundary: useProcessWorkerRuntime,
147
276
  });
148
- const returnLock = lock2({
277
+ const returnLock = stealPool?.hostReturnLock ?? lock2({
149
278
  headers: returnLockBuffers.headers,
150
279
  headerSlotStrideU32: returnLockBuffers.headerSlotStrideU32,
151
280
  LockBoundSector: returnLockBuffers.lockSector,
@@ -153,11 +282,11 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
153
282
  payloadSector: returnLockBuffers.payloadSector,
154
283
  payloadConfig: resolvedPayloadConfig,
155
284
  textCompat: returnLockBuffers.textCompat,
285
+ processBoundary: useProcessWorkerRuntime,
156
286
  });
157
- const abortSignalSAB = usesAbortSignal === true
158
- ? controlLayout.abortSignals
159
- : undefined;
160
- const abortSignals = abortSignalSAB
287
+ const abortSignalSAB = stealPool?.abortSignalSAB ??
288
+ (usesAbortSignal === true ? controlLayout.abortSignals : undefined);
289
+ const abortSignals = abortSignalSAB && stealPool === undefined
161
290
  ? signalAbortFactory({
162
291
  sab: abortSignalSAB,
163
292
  maxSignals: resolvedAbortSignalCapacity,
@@ -178,16 +307,68 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
178
307
  processRuntime: processWorkerRuntime,
179
308
  signal: signalBox.opView,
180
309
  });
181
- const queue = createHostTxQueue({
310
+ const outstandingBorrowedReturns = bufferReferenceReturn === "borrow" && typeof WeakRef === "function"
311
+ ? new Map()
312
+ : undefined;
313
+ const releaseBorrowedReturnToken = (token) => {
314
+ outstandingBorrowedReturns?.delete(token);
315
+ worker?.postMessage?.(createBufferReferenceReturnReleaseMessage(token));
316
+ };
317
+ const revokeOutstandingBorrowedReturns = (copyBytes) => {
318
+ if (outstandingBorrowedReturns === undefined)
319
+ return;
320
+ for (const [token, record] of outstandingBorrowedReturns) {
321
+ try {
322
+ const ref = record.ref.deref();
323
+ if (ref !== undefined) {
324
+ ref.revokeBorrow(copyBytes);
325
+ continue;
326
+ }
327
+ const aliasBuffer = record.aliasBuffer?.deref();
328
+ if (aliasBuffer !== undefined &&
329
+ detachArrayBufferBestEffort(record.runtime, aliasBuffer) &&
330
+ copyBytes) {
331
+ releaseBorrowedReturnToken(token);
332
+ }
333
+ }
334
+ catch {
335
+ // best effort
336
+ }
337
+ }
338
+ outstandingBorrowedReturns.clear();
339
+ };
340
+ const returnHooks = bufferReferenceReturn === "borrow"
341
+ ? {
342
+ release: releaseBorrowedReturnToken,
343
+ track: (ref, token, aliasBuffer) => {
344
+ if (outstandingBorrowedReturns === undefined)
345
+ return;
346
+ const record = outstandingBorrowedReturns.get(token);
347
+ if (record !== undefined) {
348
+ if (aliasBuffer !== undefined) {
349
+ record.aliasBuffer = new WeakRef(aliasBuffer);
350
+ }
351
+ return;
352
+ }
353
+ outstandingBorrowedReturns.set(token, {
354
+ ref: new WeakRef(ref),
355
+ aliasBuffer: aliasBuffer === undefined
356
+ ? undefined
357
+ : new WeakRef(aliasBuffer),
358
+ runtime: ref.runtime,
359
+ });
360
+ },
361
+ }
362
+ : undefined;
363
+ const queue = stealPool?.sharedQueue ?? createHostTxQueue({
182
364
  lock,
183
365
  returnLock,
184
366
  abortSignals,
185
- releaseBufferReferenceReturn: bufferReferenceReturn === "borrow"
186
- ? (token) => {
187
- worker?.postMessage?.(createBufferReferenceReturnReleaseMessage(token));
188
- }
189
- : undefined,
367
+ releaseBufferReferenceReturn: returnHooks,
190
368
  });
369
+ if (stealPool !== undefined) {
370
+ stealPool.sharedQueue.setReturnHooks(stealPool.consumerId, returnHooks);
371
+ }
191
372
  const { enqueue, rejectAll, txIdle, } = queue;
192
373
  const thisSignal = signalBox.opView;
193
374
  const a_add = Atomics.add;
@@ -207,16 +388,21 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
207
388
  let dispatchSend = () => { };
208
389
  const send = () => dispatchSend();
209
390
  let channelHandler;
210
- const ownsChannel = sharedChannelHandler === undefined;
391
+ const ownsChannel = sharedChannelHandler === undefined &&
392
+ stealPool === undefined;
211
393
  const ownChannel = sharedChannelHandler ?? new ChannelHandler();
212
- const { check: dispatcherCheck } = hostDispatcherLoop({
213
- signalBox,
214
- queue,
215
- channelHandler: ownChannel,
216
- dispatcherOptions: host,
217
- notifySignal: nativeNotifySignal,
218
- });
219
- if (ownsChannel) {
394
+ // Under stealing there is one queue, so one dispatcher drives it from the
395
+ // pool; a per-lane dispatcher would double-drain the shared registry.
396
+ const { check: dispatcherCheck } = stealPool !== undefined
397
+ ? { check: undefined }
398
+ : hostDispatcherLoop({
399
+ signalBox,
400
+ queue,
401
+ channelHandler: ownChannel,
402
+ dispatcherOptions: host,
403
+ notifySignal: nativeNotifySignal,
404
+ });
405
+ if (ownsChannel && dispatcherCheck !== undefined) {
220
406
  ownChannel.open(dispatcherCheck);
221
407
  channelHandler = ownChannel;
222
408
  dispatchSend = () => {
@@ -240,9 +426,8 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
240
426
  const workerDataPayload = {
241
427
  sab: signals.sab,
242
428
  abortSignalSAB,
243
- abortSignalMax: usesAbortSignal === true
244
- ? resolvedAbortSignalCapacity
245
- : undefined,
429
+ abortSignalMax: stealPool?.abortSignalMax ??
430
+ (usesAbortSignal === true ? resolvedAbortSignalCapacity : undefined),
246
431
  list,
247
432
  ids,
248
433
  names,
@@ -257,6 +442,11 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
257
442
  payloadConfig: resolvedPayloadConfig,
258
443
  bufferReferenceReturn,
259
444
  permission,
445
+ steal: stealPool === undefined ? undefined : {
446
+ consumers: stealPool.consumers,
447
+ consumerId: stealPool.consumerId,
448
+ regionLanes: stealPool.regionLanes,
449
+ },
260
450
  };
261
451
  const baseWorkerOptions = {
262
452
  //@ts-ignore Reason
@@ -325,29 +515,51 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
325
515
  worker.postMessage?.(workerDataPayload);
326
516
  }
327
517
  let closedReason;
328
- const markWorkerClosed = (reason) => {
518
+ const deactivateStealConsumer = () => {
519
+ stealPool?.hostSubmitLock.deactivateStealConsumer(stealPool.consumerId);
520
+ };
521
+ const terminateFailedWorker = () => {
522
+ try {
523
+ worker.unref?.();
524
+ void Promise.resolve(worker.terminate())
525
+ .catch(() => { })
526
+ .finally(deactivateStealConsumer);
527
+ }
528
+ catch {
529
+ deactivateStealConsumer();
530
+ }
531
+ };
532
+ // `workerBytesReadable`: while the worker is still alive its pinned bytes
533
+ // can be copied out; after a crash/exit they are gone and borrows are
534
+ // detached so stale reads fail loud instead of aliasing freed memory.
535
+ const markWorkerClosed = (reason, workerBytesReadable = false) => {
329
536
  if (closedReason)
330
537
  return;
331
538
  closedReason = reason;
539
+ revokeOutstandingBorrowedReturns(workerBytesReadable);
332
540
  rejectAll(reason);
333
541
  channelHandler?.close();
334
542
  };
335
543
  const onWorkerMessage = (message) => {
336
544
  if (!isWorkerFatalMessage(message))
337
545
  return;
338
- markWorkerClosed(`Worker startup failed: ${message[WORKER_FATAL_MESSAGE_KEY]}`);
339
- terminateWorkerQuietly(worker);
546
+ markWorkerClosed(`Worker startup failed: ${message[WORKER_FATAL_MESSAGE_KEY]}`, true);
547
+ terminateFailedWorker();
340
548
  };
341
549
  const onWorkerError = (error) => {
342
550
  const message = String(error?.message ?? error);
343
551
  markWorkerClosed(`Worker crashed: ${message}`);
552
+ terminateFailedWorker();
344
553
  };
345
554
  const nodeWorker = worker;
346
555
  if (typeof nodeWorker.on === "function") {
347
556
  nodeWorker.on("message", onWorkerMessage);
348
557
  nodeWorker.on("error", onWorkerError);
349
558
  nodeWorker.on("exit", (code) => {
350
- if (typeof code === "number" && code === 0)
559
+ // Exit is the point at which this endpoint can no longer write WANT, so
560
+ // survivors may safely ignore any intent it left behind.
561
+ deactivateStealConsumer();
562
+ if (closedReason !== undefined)
351
563
  return;
352
564
  const normalized = typeof code === "number" ? code : -1;
353
565
  markWorkerClosed(`Worker exited with code ${normalized}`);
@@ -388,15 +600,17 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostD
388
600
  txIdle,
389
601
  call,
390
602
  kills: async () => {
391
- markWorkerClosed("Thread closed");
603
+ markWorkerClosed("Thread closed", true);
392
604
  terminateWorkerQuietly(worker);
393
605
  cleanupProcessWorkerMemoryQuietly(processWorkerMemory);
394
606
  },
395
607
  lock,
396
608
  processSharedMemoryBackings: processSharedMemory?.backings,
397
609
  dispatcherCheck,
398
- laneWake: sharedChannelHandler !== undefined ? laneWake : undefined,
399
- bindSend: sharedChannelHandler !== undefined
610
+ laneWake: sharedChannelHandler !== undefined || stealPool !== undefined
611
+ ? laneWake
612
+ : undefined,
613
+ bindSend: sharedChannelHandler !== undefined || stealPool !== undefined
400
614
  ? ((fn) => void (dispatchSend = fn))
401
615
  : undefined,
402
616
  };
@@ -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,7 +29,7 @@ export type ProcessWorkerMemoryLayout = {
36
29
  returnPayload: SharedBufferSource;
37
30
  cleanup: () => void;
38
31
  };
39
- export type ProcessWorkerRuntime = NonNullable<WorkerSettings["processRuntime"]>;
32
+ export type ProcessWorkerRuntime = "bun" | "deno" | "node";
40
33
  export type ProcessWorkerCommandPrefix = NonNullable<WorkerSettings["processCommandPrefix"]>;
41
34
  export type ResolvedProcessSharedMemorySettings = {
42
35
  mode: "inherit" | "named";
@@ -60,8 +53,6 @@ type ProcessSharedMemoryAllocator = {
60
53
  createBuffer: (byteLength: number) => SharedArrayBuffer;
61
54
  backings: ProcessSharedMemoryBacking[];
62
55
  };
63
- export declare const toWorkerSafeExecArgv: (flags: string[] | undefined) => string[] | undefined;
64
- export declare const toWorkerCompatExecArgv: (flags: string[] | undefined) => string[] | undefined;
65
56
  export declare const createProcessSharedMemoryAllocator: (debug: DebugOptions | undefined) => ProcessSharedMemoryAllocator | undefined;
66
57
  export declare const createProcessWorkerMemoryLayout: ({ signalBytes, abortBytes, payloadBytes, thread, sharedMemory, }: {
67
58
  signalBytes: number;
@@ -70,11 +61,11 @@ export declare const createProcessWorkerMemoryLayout: ({ signalBytes, abortBytes
70
61
  thread: number;
71
62
  sharedMemory: ResolvedProcessSharedMemorySettings;
72
63
  }) => ProcessWorkerMemoryLayout;
73
- export declare const serializeWorkerBootstrapData: (options: WorkerSettings) => WorkerSettings;
74
64
  export declare const toProcessWorkerBootPayload: (workerData: WorkerData, memory: ProcessWorkerMemoryLayout) => ProcessWorkerBootPayload;
75
65
  export declare const readProcessWorkerRuntime: (options: WorkerSettings | undefined) => ProcessWorkerRuntime;
76
66
  export declare const readProcessWorkerCommandPrefix: (options: WorkerSettings | undefined) => ProcessWorkerCommandPrefix | undefined;
77
67
  export declare const readProcessSharedMemorySettings: (options: WorkerSettings | undefined) => ResolvedProcessSharedMemorySettings;
68
+ export declare const readProcessWorkerNodeMajor: (options: WorkerSettings | undefined) => number | undefined;
78
69
  export declare const createProcessWorkerNativeSignalNotifier: ({ processRuntime, signal, }: {
79
70
  processRuntime: ProcessWorkerRuntime | undefined;
80
71
  signal: Int32Array;
@@ -87,6 +78,5 @@ export declare const spawnProcessWorker: (options: {
87
78
  commandPrefix?: ProcessWorkerCommandPrefix;
88
79
  permission?: WorkerData["permission"];
89
80
  }) => SpawnedWorker & NodeWorkerLike;
90
- export declare const terminateWorkerQuietly: (worker: SpawnedWorker) => void;
91
81
  export declare const cleanupProcessWorkerMemoryQuietly: (memory: ProcessWorkerMemoryLayout | undefined) => void;
92
82
  export {};