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.
- package/BROWSER.md +134 -0
- package/README.md +94 -6
- package/knitting.browser.d.ts +6 -0
- package/knitting.browser.js +1 -0
- package/package.json +11 -2
- package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
- package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
- package/src/api.js +136 -25
- package/src/common/runtime.d.ts +2 -1
- package/src/common/runtime.js +8 -1
- package/src/common/task-source.d.ts +9 -0
- package/src/common/task-source.js +15 -0
- package/src/memory/lock.d.ts +37 -1
- package/src/memory/lock.js +262 -5
- package/src/memory/payloadCodec.js +2 -6
- package/src/memory/regionRegistry.d.ts +1 -3
- package/src/memory/regionRegistry.js +16 -14
- package/src/permission/protocol.js +5 -1
- package/src/runtime/dispatcher.d.ts +11 -8
- package/src/runtime/dispatcher.js +163 -21
- package/src/runtime/inline-executor.js +6 -1
- package/src/runtime/pool.d.ts +127 -4
- package/src/runtime/pool.js +262 -73
- package/src/runtime/process-worker.d.ts +24 -12
- package/src/runtime/process-worker.js +185 -115
- package/src/runtime/tx-queue.d.ts +17 -1
- package/src/runtime/tx-queue.js +120 -15
- package/src/runtime/worker-common.d.ts +13 -0
- package/src/runtime/worker-common.js +103 -0
- package/src/types.d.ts +75 -4
- package/src/worker/loop.js +34 -5
- package/src/worker/rx-queue.d.ts +11 -1
- package/src/worker/rx-queue.js +4 -1
- package/src/worker/timers.d.ts +8 -1
- package/src/worker/timers.js +20 -17
package/src/runtime/pool.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHostTxQueue } from "./tx-queue.js";
|
|
2
|
-
import { cleanupProcessWorkerMemoryQuietly, createProcessSharedMemoryAllocator, createProcessWorkerMemoryLayout, createProcessWorkerNativeSignalNotifier, readProcessSharedMemorySettings, readProcessWorkerCommandPrefix, readProcessWorkerRuntime,
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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 =
|
|
174
|
-
? controlLayout.abortSignals
|
|
175
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
467
|
-
|
|
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
|
|
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 {};
|