knitting 0.1.46 → 0.1.50
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/README.md +274 -77
- package/map.md +9 -3
- package/package.json +2 -1
- package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +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_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
- package/scripts/build-native-addons.ts +31 -5
- package/src/api.js +37 -13
- package/src/common/task-source.d.ts +1 -0
- package/src/common/task-source.js +1 -0
- package/src/connections/bun.d.ts +2 -0
- package/src/connections/bun.js +64 -9
- package/src/connections/deno.d.ts +2 -0
- package/src/connections/deno.js +64 -9
- package/src/connections/file-descriptor.d.ts +2 -0
- package/src/connections/file-descriptor.js +24 -2
- package/src/connections/node.d.ts +2 -1
- package/src/connections/node.js +17 -14
- package/src/connections/posix.d.ts +1 -0
- package/src/connections/posix.js +6 -0
- package/src/connections/process-shared-buffer.d.ts +3 -1
- package/src/connections/process-shared-buffer.js +17 -13
- package/src/connections/types.d.ts +2 -0
- package/src/connections/windows.d.ts +28 -0
- package/src/connections/windows.js +224 -0
- package/src/knitting_shared_memory.cc +310 -24
- package/src/knitting_windows_shared_memory.cc +156 -0
- package/src/memory/payloadCodec.js +1 -0
- package/src/runtime/pool.d.ts +1 -13
- package/src/runtime/pool.js +10 -542
- package/src/runtime/process-worker.d.ts +92 -0
- package/src/runtime/process-worker.js +670 -0
- package/src/runtime/tx-queue.d.ts +1 -1
- package/src/runtime/tx-queue.js +3 -1
- package/src/shared/abortSignal.d.ts +1 -1
- package/src/shared/abortSignal.js +10 -2
- package/src/types.d.ts +49 -3
- package/src/worker/bootstrap.d.ts +5 -0
- package/src/worker/bootstrap.js +78 -0
- package/src/worker/composable-runners.js +0 -8
- package/src/worker/loop.js +19 -154
- package/src/worker/process-worker-bootstrap.d.ts +8 -0
- package/src/worker/process-worker-bootstrap.js +159 -0
- package/src/worker/timers.js +19 -5
package/src/runtime/pool.js
CHANGED
|
@@ -1,70 +1,19 @@
|
|
|
1
|
-
// main.ts
|
|
2
|
-
import { fileURLToPath as fileURLToPathCompat } from "node:url";
|
|
3
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";
|
|
4
3
|
import { createSharedMemoryTransport, TRANSPORT_SIGNAL_BYTES, } from "../ipc/transport/shared-memory.js";
|
|
5
4
|
import { ChannelHandler, hostDispatcherLoop } from "./dispatcher.js";
|
|
6
5
|
import { HEADER_SLOT_STRIDE_U32, lock2, LOCK_SECTOR_BYTE_LENGTH, LockBound, } from "../memory/lock.js";
|
|
7
6
|
import "../worker/loop.js";
|
|
8
|
-
import { createSharedArrayBuffer, createWasmSharedArrayBuffer,
|
|
9
|
-
import { HAS_NODE_WORKER_THREADS,
|
|
10
|
-
import { toSharedBufferRegion, } from "../common/shared-buffer-region.js";
|
|
7
|
+
import { createSharedArrayBuffer, createWasmSharedArrayBuffer, } from "../common/runtime.js";
|
|
8
|
+
import { HAS_NODE_WORKER_THREADS, RUNTIME_WORKER, } from "../common/worker-runtime.js";
|
|
11
9
|
import { probeLockBufferTextCompat } from "../common/shared-buffer-text.js";
|
|
12
10
|
import { signalAbortFactory } from "../shared/abortSignal.js";
|
|
13
|
-
import {
|
|
11
|
+
import { createLockControlCarpet } from "../memory/byte-carpet.js";
|
|
14
12
|
import { resolvePayloadBufferOptions, } from "../memory/payload-config.js";
|
|
15
|
-
import { getNodeBuiltinModule, getNodeProcess, } from "../common/node-compat.js";
|
|
16
|
-
import { createBunConnectionPrimitives } from "../connections/bun.js";
|
|
17
|
-
import { createDenoConnectionPrimitives } from "../connections/deno.js";
|
|
18
|
-
import { FileDescriptor, ProcessSharedBuffer, } from "../connections/index.js";
|
|
19
|
-
import { createNodeConnectionPrimitives, loadNodeFutexAddon, } from "../connections/node.js";
|
|
20
|
-
import { loadNodeNativeAddon } from "../connections/node-addons.js";
|
|
21
|
-
import { assertPosixSharedMemoryPlatform, detectPosixPlatform, } from "../connections/posix.js";
|
|
22
13
|
const WORKER_FATAL_MESSAGE_KEY = "__knittingWorkerFatal";
|
|
23
|
-
const execFlagKey = (flag) => flag.split("=", 1)[0];
|
|
24
|
-
const NODE_PERMISSION_EXEC_FLAGS = new Set([
|
|
25
|
-
"--permission",
|
|
26
|
-
"--experimental-permission",
|
|
27
|
-
"--allow-fs-read",
|
|
28
|
-
"--allow-fs-write",
|
|
29
|
-
"--allow-worker",
|
|
30
|
-
"--allow-child-process",
|
|
31
|
-
"--allow-addons",
|
|
32
|
-
"--allow-wasi",
|
|
33
|
-
]);
|
|
34
|
-
const NODE_WORKER_SAFE_EXEC_FLAGS = new Set([
|
|
35
|
-
"--experimental-transform-types",
|
|
36
|
-
"--expose-gc",
|
|
37
|
-
"--no-warnings",
|
|
38
|
-
...NODE_PERMISSION_EXEC_FLAGS,
|
|
39
|
-
]);
|
|
40
14
|
const isWorkerFatalMessage = (value) => !!value &&
|
|
41
15
|
typeof value === "object" &&
|
|
42
16
|
typeof value[WORKER_FATAL_MESSAGE_KEY] === "string";
|
|
43
|
-
const isNodeWorkerSafeExecFlag = (flag) => NODE_WORKER_SAFE_EXEC_FLAGS.has(execFlagKey(flag));
|
|
44
|
-
const isNodePermissionExecFlag = (flag) => NODE_PERMISSION_EXEC_FLAGS.has(execFlagKey(flag));
|
|
45
|
-
const toWorkerSafeExecArgv = (flags) => {
|
|
46
|
-
if (!flags || flags.length === 0)
|
|
47
|
-
return undefined;
|
|
48
|
-
const filtered = flags.filter(isNodeWorkerSafeExecFlag);
|
|
49
|
-
if (filtered.length === 0)
|
|
50
|
-
return undefined;
|
|
51
|
-
const seen = new Set();
|
|
52
|
-
const deduped = [];
|
|
53
|
-
for (const flag of filtered) {
|
|
54
|
-
if (seen.has(flag))
|
|
55
|
-
continue;
|
|
56
|
-
seen.add(flag);
|
|
57
|
-
deduped.push(flag);
|
|
58
|
-
}
|
|
59
|
-
return deduped;
|
|
60
|
-
};
|
|
61
|
-
const toWorkerCompatExecArgv = (flags) => {
|
|
62
|
-
const safe = toWorkerSafeExecArgv(flags);
|
|
63
|
-
if (!safe || safe.length === 0)
|
|
64
|
-
return undefined;
|
|
65
|
-
const compat = safe.filter((flag) => !isNodePermissionExecFlag(flag));
|
|
66
|
-
return compat.length > 0 ? compat : undefined;
|
|
67
|
-
};
|
|
68
17
|
// Keep idle workers self-healing if an Atomics.notify wake is missed.
|
|
69
18
|
const DEFAULT_WORKER_PARK_MS = 1;
|
|
70
19
|
const withDefaultWorkerTimers = (options) => {
|
|
@@ -79,508 +28,25 @@ const withDefaultWorkerTimers = (options) => {
|
|
|
79
28
|
},
|
|
80
29
|
};
|
|
81
30
|
};
|
|
82
|
-
const toProcessSharedMemorySize = (byteLength) => {
|
|
83
|
-
if (!Number.isFinite(byteLength) || byteLength <= 0) {
|
|
84
|
-
throw new RangeError("process shared memory byteLength must be positive");
|
|
85
|
-
}
|
|
86
|
-
const size = Math.trunc(byteLength);
|
|
87
|
-
return size + ((64 - (size % 64)) % 64);
|
|
88
|
-
};
|
|
89
|
-
const createProcessSharedMemoryAllocator = (debug) => {
|
|
90
|
-
if (RUNTIME !== "node")
|
|
91
|
-
return undefined;
|
|
92
|
-
try {
|
|
93
|
-
assertPosixSharedMemoryPlatform("Process-shared memory allocator");
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
let addon;
|
|
99
|
-
try {
|
|
100
|
-
const nodeModule = getNodeBuiltinModule("node:module");
|
|
101
|
-
if (nodeModule === undefined)
|
|
102
|
-
return undefined;
|
|
103
|
-
const require = nodeModule.createRequire(import.meta.url);
|
|
104
|
-
addon = loadNodeNativeAddon(require, "knitting_shared_memory");
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
if (debug?.extras === true) {
|
|
108
|
-
console.warn("Process-shared memory allocator unavailable; falling back to SharedArrayBuffer.", error);
|
|
109
|
-
}
|
|
110
|
-
return undefined;
|
|
111
|
-
}
|
|
112
|
-
const backings = [];
|
|
113
|
-
return {
|
|
114
|
-
backings,
|
|
115
|
-
createBuffer: (byteLength) => {
|
|
116
|
-
const mapping = addon.createSharedMemory(toProcessSharedMemorySize(byteLength));
|
|
117
|
-
backings.push({
|
|
118
|
-
...mapping,
|
|
119
|
-
runtime: "node",
|
|
120
|
-
buffer: mapping.sab,
|
|
121
|
-
kind: "shared-array-buffer",
|
|
122
|
-
byteLength: mapping.sab.byteLength,
|
|
123
|
-
});
|
|
124
|
-
return mapping.sab;
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
};
|
|
128
|
-
const PROCESS_WORKER_CHILD_FD = 0;
|
|
129
|
-
const DEFAULT_BUN_BINARY = "bun";
|
|
130
|
-
const DEFAULT_DENO_BINARY = "deno";
|
|
131
|
-
const DEFAULT_NODE_BINARY = "node";
|
|
132
|
-
const DENO_PROCESS_WORKER_BOOT_ENV_ALLOW = [
|
|
133
|
-
RUNTIME_PROCESS_WORKER_ENV,
|
|
134
|
-
RUNTIME_PROCESS_WORKER_BOOT_ENV,
|
|
135
|
-
].join(",");
|
|
136
|
-
const DENO_PROCESS_WORKER_INTERNAL_FLAGS = [
|
|
137
|
-
`--allow-env=${DENO_PROCESS_WORKER_BOOT_ENV_ALLOW}`,
|
|
138
|
-
"--allow-ffi",
|
|
139
|
-
];
|
|
140
|
-
const NODE_PROCESS_WORKER_EXEC_ARGV = [
|
|
141
|
-
"--no-warnings",
|
|
142
|
-
"--experimental-transform-types",
|
|
143
|
-
];
|
|
144
31
|
const withFixedPayloadConfig = (config) => ({
|
|
145
32
|
...config,
|
|
146
33
|
mode: "fixed",
|
|
147
34
|
payloadInitialBytes: config.payloadMaxByteLength,
|
|
148
35
|
});
|
|
149
|
-
const getProcessWorkerSharedMemoryPrimitives = () => {
|
|
150
|
-
assertPosixSharedMemoryPlatform("Process worker runtime");
|
|
151
|
-
switch (RUNTIME) {
|
|
152
|
-
case "bun":
|
|
153
|
-
return createBunConnectionPrimitives();
|
|
154
|
-
case "deno":
|
|
155
|
-
return createDenoConnectionPrimitives();
|
|
156
|
-
case "node":
|
|
157
|
-
return createNodeConnectionPrimitives();
|
|
158
|
-
default:
|
|
159
|
-
throw new Error("process worker runtime needs Node, Deno, or Bun shared memory primitives");
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
const createProcessWorkerMemoryLayout = ({ signalBytes, abortBytes, payloadBytes, thread, }) => {
|
|
163
|
-
const carpet = createByteCarpet();
|
|
164
|
-
const signalsSlice = carpet.take("signals", signalBytes);
|
|
165
|
-
const requestLockSlice = carpet.take("requestLockSector", LOCK_SECTOR_BYTE_LENGTH);
|
|
166
|
-
const returnLockSlice = carpet.take("returnLockSector", LOCK_SECTOR_BYTE_LENGTH);
|
|
167
|
-
const requestHeadersSlice = carpet.take("requestHeaders", getHeaderBlockByteLength({
|
|
168
|
-
slotCount: LockBound.slots,
|
|
169
|
-
slotStrideU32: HEADER_SLOT_STRIDE_U32,
|
|
170
|
-
alignTo: 64,
|
|
171
|
-
}));
|
|
172
|
-
const returnHeadersSlice = carpet.take("returnHeaders", getHeaderBlockByteLength({
|
|
173
|
-
slotCount: LockBound.slots,
|
|
174
|
-
slotStrideU32: HEADER_SLOT_STRIDE_U32,
|
|
175
|
-
alignTo: 64,
|
|
176
|
-
}));
|
|
177
|
-
const abortSignalsSlice = carpet.take("abortSignals", abortBytes);
|
|
178
|
-
const requestPayloadSlice = carpet.take("requestPayload", payloadBytes);
|
|
179
|
-
const returnPayloadSlice = carpet.take("returnPayload", payloadBytes);
|
|
180
|
-
const primitives = getProcessWorkerSharedMemoryPrimitives();
|
|
181
|
-
const mapping = primitives.createSharedMemory({
|
|
182
|
-
size: carpet.byteLength(),
|
|
183
|
-
name: `knitting_process_worker_${thread}`,
|
|
184
|
-
});
|
|
185
|
-
const buffer = mapping.buffer;
|
|
186
|
-
const bind = (slice) => makeSharedBufferRegion(buffer, slice.byteOffset, slice.byteLength);
|
|
187
|
-
const controlLayout = {
|
|
188
|
-
controlSAB: buffer,
|
|
189
|
-
signals: bind(signalsSlice),
|
|
190
|
-
abortSignals: bind(abortSignalsSlice),
|
|
191
|
-
lock: {
|
|
192
|
-
headers: bind(requestHeadersSlice),
|
|
193
|
-
headerSlotStrideU32: HEADER_SLOT_STRIDE_U32,
|
|
194
|
-
lockSector: bind(requestLockSlice),
|
|
195
|
-
payloadSector: bind(requestLockSlice),
|
|
196
|
-
},
|
|
197
|
-
returnLock: {
|
|
198
|
-
headers: bind(returnHeadersSlice),
|
|
199
|
-
headerSlotStrideU32: HEADER_SLOT_STRIDE_U32,
|
|
200
|
-
lockSector: bind(returnLockSlice),
|
|
201
|
-
payloadSector: bind(returnLockSlice),
|
|
202
|
-
},
|
|
203
|
-
slices: carpet.slices,
|
|
204
|
-
};
|
|
205
|
-
return {
|
|
206
|
-
mapping,
|
|
207
|
-
descriptor: FileDescriptor.fromMapping(mapping),
|
|
208
|
-
controlLayout,
|
|
209
|
-
lockPayload: bind(requestPayloadSlice),
|
|
210
|
-
returnPayload: bind(returnPayloadSlice),
|
|
211
|
-
};
|
|
212
|
-
};
|
|
213
|
-
const toChildProcessSharedBufferMetadata = (source, descriptor) => {
|
|
214
|
-
const region = toSharedBufferRegion(source);
|
|
215
|
-
return ProcessSharedBuffer.fromDescriptor(new FileDescriptor({
|
|
216
|
-
...descriptor.toMetadata(),
|
|
217
|
-
fd: PROCESS_WORKER_CHILD_FD,
|
|
218
|
-
}), {
|
|
219
|
-
byteOffset: region.byteOffset,
|
|
220
|
-
byteLength: region.byteLength,
|
|
221
|
-
}).toMetadata();
|
|
222
|
-
};
|
|
223
|
-
const toProcessWorkerWireLockBuffers = (lock, descriptor) => ({
|
|
224
|
-
...lock,
|
|
225
|
-
headers: toChildProcessSharedBufferMetadata(lock.headers, descriptor),
|
|
226
|
-
lockSector: toChildProcessSharedBufferMetadata(lock.lockSector, descriptor),
|
|
227
|
-
payload: toChildProcessSharedBufferMetadata(lock.payload, descriptor),
|
|
228
|
-
payloadSector: toChildProcessSharedBufferMetadata(lock.payloadSector, descriptor),
|
|
229
|
-
});
|
|
230
|
-
const toProcessWorkerBootPayload = (workerData, memory) => ({
|
|
231
|
-
version: RUNTIME_PROCESS_WORKER_BOOT_VERSION,
|
|
232
|
-
workerData: {
|
|
233
|
-
...workerData,
|
|
234
|
-
sab: toChildProcessSharedBufferMetadata(workerData.sab, memory.descriptor),
|
|
235
|
-
abortSignalSAB: workerData.abortSignalSAB === undefined
|
|
236
|
-
? undefined
|
|
237
|
-
: toChildProcessSharedBufferMetadata(workerData.abortSignalSAB, memory.descriptor),
|
|
238
|
-
lock: toProcessWorkerWireLockBuffers(workerData.lock, memory.descriptor),
|
|
239
|
-
returnLock: toProcessWorkerWireLockBuffers(workerData.returnLock, memory.descriptor),
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
const toProcessWorkerPath = (specifier) => {
|
|
243
|
-
const value = specifier instanceof URL ? specifier.href : specifier;
|
|
244
|
-
if (value.startsWith("file:"))
|
|
245
|
-
return fileURLToPathCompat(value);
|
|
246
|
-
return value;
|
|
247
|
-
};
|
|
248
|
-
const readProcessWorkerRuntime = (options) => {
|
|
249
|
-
const runtime = options?.processRuntime ?? "bun";
|
|
250
|
-
if (runtime === "bun" || runtime === "deno" || runtime === "node") {
|
|
251
|
-
return runtime;
|
|
252
|
-
}
|
|
253
|
-
throw new TypeError(`Unsupported process worker runtime: ${String(runtime)}`);
|
|
254
|
-
};
|
|
255
|
-
const readProcessWorkerCommandPrefix = (options) => {
|
|
256
|
-
const prefix = options?.processCommandPrefix;
|
|
257
|
-
if (prefix === undefined)
|
|
258
|
-
return undefined;
|
|
259
|
-
if (!Array.isArray(prefix)) {
|
|
260
|
-
throw new TypeError("processCommandPrefix must be an argv array");
|
|
261
|
-
}
|
|
262
|
-
if (prefix.length === 0)
|
|
263
|
-
return undefined;
|
|
264
|
-
const out = [];
|
|
265
|
-
for (const [index, value] of prefix.entries()) {
|
|
266
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
267
|
-
throw new TypeError(`processCommandPrefix[${index}] must be a non-empty string`);
|
|
268
|
-
}
|
|
269
|
-
out.push(value);
|
|
270
|
-
}
|
|
271
|
-
return out;
|
|
272
|
-
};
|
|
273
|
-
const currentProcessEnv = () => ({
|
|
274
|
-
...getNodeProcess()?.env,
|
|
275
|
-
});
|
|
276
|
-
const processWorkerEnv = (extra) => ({
|
|
277
|
-
...currentProcessEnv(),
|
|
278
|
-
[RUNTIME_PROCESS_WORKER_ENV]: "1",
|
|
279
|
-
...extra,
|
|
280
|
-
});
|
|
281
|
-
const processWorkerBootEnv = (bootPayload) => processWorkerEnv({
|
|
282
|
-
[RUNTIME_PROCESS_WORKER_BOOT_ENV]: JSON.stringify(bootPayload),
|
|
283
|
-
});
|
|
284
|
-
const stringProcessEnv = (input) => {
|
|
285
|
-
const out = {};
|
|
286
|
-
for (const [key, value] of Object.entries(input)) {
|
|
287
|
-
if (value !== undefined)
|
|
288
|
-
out[key] = value;
|
|
289
|
-
}
|
|
290
|
-
return out;
|
|
291
|
-
};
|
|
292
|
-
const processWorkerBunBinary = (bun) => getNodeProcess()?.env?.BUN_BINARY ??
|
|
293
|
-
bun?.argv?.[0] ??
|
|
294
|
-
DEFAULT_BUN_BINARY;
|
|
295
|
-
const processWorkerDenoBinary = (deno) => getNodeProcess()?.env?.DENO_BINARY ??
|
|
296
|
-
deno?.execPath?.() ??
|
|
297
|
-
DEFAULT_DENO_BINARY;
|
|
298
|
-
const processWorkerDenoFlags = (permission) => {
|
|
299
|
-
if (permission?.enabled !== true || permission.unsafe === true) {
|
|
300
|
-
return ["-A"];
|
|
301
|
-
}
|
|
302
|
-
return [
|
|
303
|
-
...DENO_PROCESS_WORKER_INTERNAL_FLAGS,
|
|
304
|
-
...permission.deno.flags,
|
|
305
|
-
];
|
|
306
|
-
};
|
|
307
|
-
const processWorkerNodeBinary = () => {
|
|
308
|
-
const nodeProcess = getNodeProcess();
|
|
309
|
-
return nodeProcess?.env?.NODE_BINARY ??
|
|
310
|
-
(RUNTIME === "node" ? nodeProcess?.execPath : undefined) ??
|
|
311
|
-
DEFAULT_NODE_BINARY;
|
|
312
|
-
};
|
|
313
|
-
const processWorkerNodeExecArgv = () => {
|
|
314
|
-
const out = [];
|
|
315
|
-
const seen = new Set();
|
|
316
|
-
const add = (flag) => {
|
|
317
|
-
if (seen.has(flag))
|
|
318
|
-
return;
|
|
319
|
-
seen.add(flag);
|
|
320
|
-
out.push(flag);
|
|
321
|
-
};
|
|
322
|
-
for (const flag of toWorkerCompatExecArgv(getNodeProcess()?.execArgv) ?? []) {
|
|
323
|
-
add(flag);
|
|
324
|
-
}
|
|
325
|
-
for (const flag of NODE_PROCESS_WORKER_EXEC_ARGV)
|
|
326
|
-
add(flag);
|
|
327
|
-
return out;
|
|
328
|
-
};
|
|
329
|
-
const processWorkerCommand = ({ processRuntime, workerUrl, bun, deno, commandPrefix, permission, }) => {
|
|
330
|
-
const workerPath = toProcessWorkerPath(workerUrl);
|
|
331
|
-
let command;
|
|
332
|
-
if (processRuntime === "deno") {
|
|
333
|
-
command = [
|
|
334
|
-
processWorkerDenoBinary(deno),
|
|
335
|
-
"run",
|
|
336
|
-
...processWorkerDenoFlags(permission),
|
|
337
|
-
workerPath,
|
|
338
|
-
];
|
|
339
|
-
}
|
|
340
|
-
else if (processRuntime === "node") {
|
|
341
|
-
command = [
|
|
342
|
-
processWorkerNodeBinary(),
|
|
343
|
-
...processWorkerNodeExecArgv(),
|
|
344
|
-
workerPath,
|
|
345
|
-
];
|
|
346
|
-
}
|
|
347
|
-
else {
|
|
348
|
-
command = [processWorkerBunBinary(bun), workerPath];
|
|
349
|
-
}
|
|
350
|
-
return commandPrefix === undefined
|
|
351
|
-
? command
|
|
352
|
-
: [...commandPrefix, ...command];
|
|
353
|
-
};
|
|
354
|
-
const createProcessWorkerNativeSignalNotifier = ({ processRuntime, signal, }) => {
|
|
355
|
-
if (RUNTIME !== "node" || processRuntime !== "node")
|
|
356
|
-
return undefined;
|
|
357
|
-
try {
|
|
358
|
-
const futex = loadNodeFutexAddon();
|
|
359
|
-
return () => {
|
|
360
|
-
futex.wakeU32(signal.buffer, signal.byteOffset, 1);
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
catch {
|
|
364
|
-
return undefined;
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
const createProcessWorkerEventHub = () => {
|
|
368
|
-
const messageHandlers = [];
|
|
369
|
-
const errorHandlers = [];
|
|
370
|
-
const exitHandlers = [];
|
|
371
|
-
return {
|
|
372
|
-
emitMessage: (message) => {
|
|
373
|
-
for (const handler of messageHandlers)
|
|
374
|
-
handler(message);
|
|
375
|
-
},
|
|
376
|
-
emitError: (error) => {
|
|
377
|
-
for (const handler of errorHandlers)
|
|
378
|
-
handler(error);
|
|
379
|
-
},
|
|
380
|
-
emitExit: (code) => {
|
|
381
|
-
for (const handler of exitHandlers)
|
|
382
|
-
handler(code);
|
|
383
|
-
},
|
|
384
|
-
on: (event, listener) => {
|
|
385
|
-
if (event === "message")
|
|
386
|
-
messageHandlers.push(listener);
|
|
387
|
-
if (event === "error")
|
|
388
|
-
errorHandlers.push(listener);
|
|
389
|
-
if (event === "exit")
|
|
390
|
-
exitHandlers.push(listener);
|
|
391
|
-
},
|
|
392
|
-
};
|
|
393
|
-
};
|
|
394
|
-
const spawnBunHostedProcessWorker = ({ workerUrl, bootPayload, memory, processRuntime, commandPrefix, permission, }) => {
|
|
395
|
-
const bun = globalThis.Bun;
|
|
396
|
-
if (typeof bun?.spawn !== "function") {
|
|
397
|
-
throw new Error("Bun.spawn is not available for process workers");
|
|
398
|
-
}
|
|
399
|
-
const events = createProcessWorkerEventHub();
|
|
400
|
-
const nodeProcess = getNodeProcess();
|
|
401
|
-
const useIpcBoot = processRuntime === "bun" && commandPrefix === undefined;
|
|
402
|
-
const spawnOptions = {
|
|
403
|
-
cmd: processWorkerCommand({
|
|
404
|
-
processRuntime,
|
|
405
|
-
workerUrl,
|
|
406
|
-
bun,
|
|
407
|
-
commandPrefix,
|
|
408
|
-
permission,
|
|
409
|
-
}),
|
|
410
|
-
cwd: nodeProcess?.cwd?.(),
|
|
411
|
-
env: useIpcBoot
|
|
412
|
-
? processWorkerEnv()
|
|
413
|
-
: processWorkerBootEnv(bootPayload),
|
|
414
|
-
stdin: memory.mapping.fd,
|
|
415
|
-
stdout: "inherit",
|
|
416
|
-
stderr: "inherit",
|
|
417
|
-
onExit: (_subprocess, exitCode, _signalCode, error) => {
|
|
418
|
-
if (error !== undefined)
|
|
419
|
-
events.emitError(error);
|
|
420
|
-
events.emitExit(exitCode ?? -1);
|
|
421
|
-
},
|
|
422
|
-
};
|
|
423
|
-
if (useIpcBoot) {
|
|
424
|
-
spawnOptions.serialization = "advanced";
|
|
425
|
-
spawnOptions.ipc = (message) => {
|
|
426
|
-
events.emitMessage(message);
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
const child = bun.spawn(spawnOptions);
|
|
430
|
-
if (useIpcBoot) {
|
|
431
|
-
queueMicrotask(() => child.send?.(bootPayload));
|
|
432
|
-
}
|
|
433
|
-
child.exited.catch((error) => {
|
|
434
|
-
events.emitError(error);
|
|
435
|
-
});
|
|
436
|
-
return {
|
|
437
|
-
terminate: () => {
|
|
438
|
-
child.kill();
|
|
439
|
-
return child.exited.catch(() => undefined);
|
|
440
|
-
},
|
|
441
|
-
on: events.on,
|
|
442
|
-
};
|
|
443
|
-
};
|
|
444
|
-
const spawnNodeHostedProcessWorker = ({ workerUrl, bootPayload, memory, processRuntime, commandPrefix, permission, }) => {
|
|
445
|
-
const childProcess = getNodeBuiltinModule("node:child_process");
|
|
446
|
-
if (typeof childProcess?.spawn !== "function") {
|
|
447
|
-
throw new Error("node:child_process.spawn is not available");
|
|
448
|
-
}
|
|
449
|
-
const events = createProcessWorkerEventHub();
|
|
450
|
-
const useIpcBoot = processRuntime === "bun" && commandPrefix === undefined;
|
|
451
|
-
const [command, ...args] = processWorkerCommand({
|
|
452
|
-
processRuntime,
|
|
453
|
-
workerUrl,
|
|
454
|
-
commandPrefix,
|
|
455
|
-
permission,
|
|
456
|
-
});
|
|
457
|
-
const child = childProcess.spawn(command, args, {
|
|
458
|
-
cwd: getNodeProcess()?.cwd?.(),
|
|
459
|
-
env: useIpcBoot
|
|
460
|
-
? processWorkerEnv()
|
|
461
|
-
: processWorkerBootEnv(bootPayload),
|
|
462
|
-
stdio: useIpcBoot
|
|
463
|
-
? [memory.mapping.fd, "inherit", "inherit", "ipc"]
|
|
464
|
-
: [memory.mapping.fd, "inherit", "inherit"],
|
|
465
|
-
});
|
|
466
|
-
if (useIpcBoot) {
|
|
467
|
-
child.on("message", events.emitMessage);
|
|
468
|
-
queueMicrotask(() => child.send?.(bootPayload));
|
|
469
|
-
}
|
|
470
|
-
child.on("error", events.emitError);
|
|
471
|
-
child.on("exit", (code) => events.emitExit(code ?? -1));
|
|
472
|
-
return {
|
|
473
|
-
terminate: () => child.kill(),
|
|
474
|
-
unref: () => child.unref?.(),
|
|
475
|
-
on: events.on,
|
|
476
|
-
};
|
|
477
|
-
};
|
|
478
|
-
const getDenoRuntime = () => globalThis.Deno;
|
|
479
|
-
const denoFileRid = (file) => {
|
|
480
|
-
for (const symbol of Object.getOwnPropertySymbols(file)) {
|
|
481
|
-
if (String(symbol) === "Symbol(Deno.internal.rid)") {
|
|
482
|
-
const rid = file[symbol];
|
|
483
|
-
if (typeof rid === "number")
|
|
484
|
-
return rid;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
throw new Error("Deno FsFile resource id is not available");
|
|
488
|
-
};
|
|
489
|
-
const openDenoInheritedFd = (fd) => {
|
|
490
|
-
const deno = getDenoRuntime();
|
|
491
|
-
if (typeof deno?.openSync !== "function") {
|
|
492
|
-
throw new Error("Deno.openSync is not available for process workers");
|
|
493
|
-
}
|
|
494
|
-
const fdPath = detectPosixPlatform() === "linux"
|
|
495
|
-
? `/proc/self/fd/${fd}`
|
|
496
|
-
: `/dev/fd/${fd}`;
|
|
497
|
-
return deno.openSync(fdPath, { read: true, write: true });
|
|
498
|
-
};
|
|
499
|
-
const spawnDenoHostedProcessWorker = ({ workerUrl, bootPayload, memory, processRuntime, commandPrefix, permission, }) => {
|
|
500
|
-
const deno = getDenoRuntime();
|
|
501
|
-
if (typeof deno?.Command !== "function") {
|
|
502
|
-
throw new Error("Deno.Command is not available for process workers");
|
|
503
|
-
}
|
|
504
|
-
const inheritedFd = openDenoInheritedFd(memory.mapping.fd);
|
|
505
|
-
const events = createProcessWorkerEventHub();
|
|
506
|
-
const [command, ...args] = processWorkerCommand({
|
|
507
|
-
processRuntime,
|
|
508
|
-
workerUrl,
|
|
509
|
-
deno,
|
|
510
|
-
commandPrefix,
|
|
511
|
-
permission,
|
|
512
|
-
});
|
|
513
|
-
const child = new deno.Command(command, {
|
|
514
|
-
args,
|
|
515
|
-
cwd: deno.cwd?.(),
|
|
516
|
-
env: stringProcessEnv(processWorkerBootEnv(bootPayload)),
|
|
517
|
-
stdin: denoFileRid(inheritedFd),
|
|
518
|
-
stdout: "inherit",
|
|
519
|
-
stderr: "inherit",
|
|
520
|
-
}).spawn();
|
|
521
|
-
const closeInheritedFd = () => {
|
|
522
|
-
try {
|
|
523
|
-
inheritedFd.close?.();
|
|
524
|
-
}
|
|
525
|
-
catch {
|
|
526
|
-
}
|
|
527
|
-
};
|
|
528
|
-
child.status.then((status) => {
|
|
529
|
-
closeInheritedFd();
|
|
530
|
-
events.emitExit(status.code);
|
|
531
|
-
}, (error) => {
|
|
532
|
-
closeInheritedFd();
|
|
533
|
-
events.emitError(error);
|
|
534
|
-
events.emitExit(-1);
|
|
535
|
-
});
|
|
536
|
-
return {
|
|
537
|
-
terminate: () => {
|
|
538
|
-
try {
|
|
539
|
-
child.kill("SIGTERM");
|
|
540
|
-
}
|
|
541
|
-
catch {
|
|
542
|
-
}
|
|
543
|
-
return child.status.finally(closeInheritedFd);
|
|
544
|
-
},
|
|
545
|
-
on: events.on,
|
|
546
|
-
};
|
|
547
|
-
};
|
|
548
|
-
const spawnProcessWorker = (options) => {
|
|
549
|
-
switch (RUNTIME) {
|
|
550
|
-
case "bun":
|
|
551
|
-
return spawnBunHostedProcessWorker(options);
|
|
552
|
-
case "node":
|
|
553
|
-
return spawnNodeHostedProcessWorker(options);
|
|
554
|
-
case "deno":
|
|
555
|
-
return spawnDenoHostedProcessWorker(options);
|
|
556
|
-
default:
|
|
557
|
-
throw new Error("process worker runtime is only available in Node, Deno, or Bun");
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
const terminateWorkerQuietly = (worker) => {
|
|
561
|
-
try {
|
|
562
|
-
// Runaway worker termination can be slow or stuck on some runtimes; once the
|
|
563
|
-
// pool is closing it must not keep the host process alive.
|
|
564
|
-
worker.unref?.();
|
|
565
|
-
void Promise.resolve(worker.terminate()).catch(() => { });
|
|
566
|
-
}
|
|
567
|
-
catch {
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
36
|
export const spawnWorkerContext = ({ list, ids, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }) => {
|
|
571
37
|
const tsFileUrl = new URL(import.meta.url);
|
|
572
38
|
const poliWorker = RUNTIME_WORKER;
|
|
573
|
-
const resolvedWorkerOptions = withDefaultWorkerTimers(workerOptions);
|
|
39
|
+
const resolvedWorkerOptions = serializeWorkerBootstrapData(withDefaultWorkerTimers(workerOptions));
|
|
574
40
|
const useProcessWorkerRuntime = resolvedWorkerOptions.runtime === "process";
|
|
575
|
-
if (useProcessWorkerRuntime) {
|
|
576
|
-
assertPosixSharedMemoryPlatform("Process worker runtime");
|
|
577
|
-
}
|
|
578
41
|
const processWorkerRuntime = useProcessWorkerRuntime
|
|
579
42
|
? readProcessWorkerRuntime(resolvedWorkerOptions)
|
|
580
43
|
: undefined;
|
|
581
44
|
const processWorkerCommandPrefix = useProcessWorkerRuntime
|
|
582
45
|
? readProcessWorkerCommandPrefix(resolvedWorkerOptions)
|
|
583
46
|
: undefined;
|
|
47
|
+
const processSharedMemorySettings = useProcessWorkerRuntime
|
|
48
|
+
? readProcessSharedMemorySettings(resolvedWorkerOptions)
|
|
49
|
+
: undefined;
|
|
584
50
|
if (debug?.logHref === true) {
|
|
585
51
|
console.log(tsFileUrl);
|
|
586
52
|
}
|
|
@@ -627,6 +93,7 @@ export const spawnWorkerContext = ({ list, ids, sab, thread, debug, totalNumberO
|
|
|
627
93
|
abortBytes,
|
|
628
94
|
payloadBytes: resolvedPayloadConfig.payloadMaxByteLength,
|
|
629
95
|
thread,
|
|
96
|
+
sharedMemory: processSharedMemorySettings,
|
|
630
97
|
})
|
|
631
98
|
: undefined;
|
|
632
99
|
const processSharedMemory = processWorkerMemory === undefined
|
|
@@ -914,6 +381,7 @@ export const spawnWorkerContext = ({ list, ids, sab, thread, debug, totalNumberO
|
|
|
914
381
|
kills: async () => {
|
|
915
382
|
markWorkerClosed("Thread closed");
|
|
916
383
|
terminateWorkerQuietly(worker);
|
|
384
|
+
cleanupProcessWorkerMemoryQuietly(processWorkerMemory);
|
|
917
385
|
},
|
|
918
386
|
lock,
|
|
919
387
|
processSharedMemoryBackings: processSharedMemory?.backings,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type LockControlCarpet } from "../memory/byte-carpet.js";
|
|
2
|
+
import type { DebugOptions, LockBuffers, WorkerData, WorkerSettings } from "../types.js";
|
|
3
|
+
import { RUNTIME_PROCESS_WORKER_BOOT_VERSION } from "../common/worker-runtime.js";
|
|
4
|
+
import { type SharedBufferSource } from "../common/shared-buffer-region.js";
|
|
5
|
+
import { FileDescriptor, type ProcessSharedBufferMetadata } from "../connections/index.js";
|
|
6
|
+
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
|
+
type ProcessWorkerWireLockBuffers = Omit<LockBuffers, "headers" | "lockSector" | "payload" | "payloadSector"> & {
|
|
16
|
+
headers: ProcessSharedBufferMetadata;
|
|
17
|
+
lockSector: ProcessSharedBufferMetadata;
|
|
18
|
+
payload: ProcessSharedBufferMetadata;
|
|
19
|
+
payloadSector: ProcessSharedBufferMetadata;
|
|
20
|
+
};
|
|
21
|
+
type ProcessWorkerWireData = Omit<WorkerData, "sab" | "abortSignalSAB" | "lock" | "returnLock"> & {
|
|
22
|
+
sab: ProcessSharedBufferMetadata;
|
|
23
|
+
abortSignalSAB?: ProcessSharedBufferMetadata;
|
|
24
|
+
lock: ProcessWorkerWireLockBuffers;
|
|
25
|
+
returnLock: ProcessWorkerWireLockBuffers;
|
|
26
|
+
};
|
|
27
|
+
export type ProcessWorkerBootPayload = {
|
|
28
|
+
version: typeof RUNTIME_PROCESS_WORKER_BOOT_VERSION;
|
|
29
|
+
workerData: ProcessWorkerWireData;
|
|
30
|
+
};
|
|
31
|
+
export type ProcessWorkerMemoryLayout = {
|
|
32
|
+
mapping: SharedMemoryMapping<SharedMemoryBuffer>;
|
|
33
|
+
descriptor: FileDescriptor;
|
|
34
|
+
controlLayout: LockControlCarpet;
|
|
35
|
+
lockPayload: SharedBufferSource;
|
|
36
|
+
returnPayload: SharedBufferSource;
|
|
37
|
+
cleanup: () => void;
|
|
38
|
+
};
|
|
39
|
+
export type ProcessWorkerRuntime = NonNullable<WorkerSettings["processRuntime"]>;
|
|
40
|
+
export type ProcessWorkerCommandPrefix = NonNullable<WorkerSettings["processCommandPrefix"]>;
|
|
41
|
+
export type ResolvedProcessSharedMemorySettings = {
|
|
42
|
+
mode: "inherit" | "named";
|
|
43
|
+
namePrefix?: string;
|
|
44
|
+
unlinkOnShutdown: boolean;
|
|
45
|
+
};
|
|
46
|
+
type ProcessSharedMemoryNativeMapping = {
|
|
47
|
+
sab: SharedArrayBuffer;
|
|
48
|
+
fd: number;
|
|
49
|
+
name?: string;
|
|
50
|
+
size: number;
|
|
51
|
+
baseAddressMod64?: number;
|
|
52
|
+
};
|
|
53
|
+
export type ProcessSharedMemoryBacking = ProcessSharedMemoryNativeMapping & {
|
|
54
|
+
runtime: "node";
|
|
55
|
+
buffer: SharedArrayBuffer;
|
|
56
|
+
kind: "shared-array-buffer";
|
|
57
|
+
byteLength: number;
|
|
58
|
+
};
|
|
59
|
+
type ProcessSharedMemoryAllocator = {
|
|
60
|
+
createBuffer: (byteLength: number) => SharedArrayBuffer;
|
|
61
|
+
backings: ProcessSharedMemoryBacking[];
|
|
62
|
+
};
|
|
63
|
+
export declare const toWorkerSafeExecArgv: (flags: string[] | undefined) => string[] | undefined;
|
|
64
|
+
export declare const toWorkerCompatExecArgv: (flags: string[] | undefined) => string[] | undefined;
|
|
65
|
+
export declare const createProcessSharedMemoryAllocator: (debug: DebugOptions | undefined) => ProcessSharedMemoryAllocator | undefined;
|
|
66
|
+
export declare const createProcessWorkerMemoryLayout: ({ signalBytes, abortBytes, payloadBytes, thread, sharedMemory, }: {
|
|
67
|
+
signalBytes: number;
|
|
68
|
+
abortBytes: number;
|
|
69
|
+
payloadBytes: number;
|
|
70
|
+
thread: number;
|
|
71
|
+
sharedMemory: ResolvedProcessSharedMemorySettings;
|
|
72
|
+
}) => ProcessWorkerMemoryLayout;
|
|
73
|
+
export declare const serializeWorkerBootstrapData: (options: WorkerSettings) => WorkerSettings;
|
|
74
|
+
export declare const toProcessWorkerBootPayload: (workerData: WorkerData, memory: ProcessWorkerMemoryLayout) => ProcessWorkerBootPayload;
|
|
75
|
+
export declare const readProcessWorkerRuntime: (options: WorkerSettings | undefined) => ProcessWorkerRuntime;
|
|
76
|
+
export declare const readProcessWorkerCommandPrefix: (options: WorkerSettings | undefined) => ProcessWorkerCommandPrefix | undefined;
|
|
77
|
+
export declare const readProcessSharedMemorySettings: (options: WorkerSettings | undefined) => ResolvedProcessSharedMemorySettings;
|
|
78
|
+
export declare const createProcessWorkerNativeSignalNotifier: ({ processRuntime, signal, }: {
|
|
79
|
+
processRuntime: ProcessWorkerRuntime | undefined;
|
|
80
|
+
signal: Int32Array;
|
|
81
|
+
}) => (() => void) | undefined;
|
|
82
|
+
export declare const spawnProcessWorker: (options: {
|
|
83
|
+
workerUrl: string | URL;
|
|
84
|
+
bootPayload: ProcessWorkerBootPayload;
|
|
85
|
+
memory: ProcessWorkerMemoryLayout;
|
|
86
|
+
processRuntime: ProcessWorkerRuntime;
|
|
87
|
+
commandPrefix?: ProcessWorkerCommandPrefix;
|
|
88
|
+
permission?: WorkerData["permission"];
|
|
89
|
+
}) => SpawnedWorker & NodeWorkerLike;
|
|
90
|
+
export declare const terminateWorkerQuietly: (worker: SpawnedWorker) => void;
|
|
91
|
+
export declare const cleanupProcessWorkerMemoryQuietly: (memory: ProcessWorkerMemoryLayout | undefined) => void;
|
|
92
|
+
export {};
|