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.
Files changed (52) hide show
  1. package/README.md +274 -77
  2. package/map.md +9 -3
  3. package/package.json +2 -1
  4. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  5. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  6. package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
  7. package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
  8. package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
  9. package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
  10. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  11. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  12. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  13. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  14. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  15. package/scripts/build-native-addons.ts +31 -5
  16. package/src/api.js +37 -13
  17. package/src/common/task-source.d.ts +1 -0
  18. package/src/common/task-source.js +1 -0
  19. package/src/connections/bun.d.ts +2 -0
  20. package/src/connections/bun.js +64 -9
  21. package/src/connections/deno.d.ts +2 -0
  22. package/src/connections/deno.js +64 -9
  23. package/src/connections/file-descriptor.d.ts +2 -0
  24. package/src/connections/file-descriptor.js +24 -2
  25. package/src/connections/node.d.ts +2 -1
  26. package/src/connections/node.js +17 -14
  27. package/src/connections/posix.d.ts +1 -0
  28. package/src/connections/posix.js +6 -0
  29. package/src/connections/process-shared-buffer.d.ts +3 -1
  30. package/src/connections/process-shared-buffer.js +17 -13
  31. package/src/connections/types.d.ts +2 -0
  32. package/src/connections/windows.d.ts +28 -0
  33. package/src/connections/windows.js +224 -0
  34. package/src/knitting_shared_memory.cc +310 -24
  35. package/src/knitting_windows_shared_memory.cc +156 -0
  36. package/src/memory/payloadCodec.js +1 -0
  37. package/src/runtime/pool.d.ts +1 -13
  38. package/src/runtime/pool.js +10 -542
  39. package/src/runtime/process-worker.d.ts +92 -0
  40. package/src/runtime/process-worker.js +670 -0
  41. package/src/runtime/tx-queue.d.ts +1 -1
  42. package/src/runtime/tx-queue.js +3 -1
  43. package/src/shared/abortSignal.d.ts +1 -1
  44. package/src/shared/abortSignal.js +10 -2
  45. package/src/types.d.ts +49 -3
  46. package/src/worker/bootstrap.d.ts +5 -0
  47. package/src/worker/bootstrap.js +78 -0
  48. package/src/worker/composable-runners.js +0 -8
  49. package/src/worker/loop.js +19 -154
  50. package/src/worker/process-worker-bootstrap.d.ts +8 -0
  51. package/src/worker/process-worker-bootstrap.js +159 -0
  52. package/src/worker/timers.js +19 -5
@@ -0,0 +1,670 @@
1
+ import { fileURLToPath as fileURLToPathCompat } from "node:url";
2
+ import { HEADER_SLOT_STRIDE_U32, LOCK_SECTOR_BYTE_LENGTH, LockBound, } from "../memory/lock.js";
3
+ import { createByteCarpet, getHeaderBlockByteLength, makeSharedBufferRegion, } from "../memory/byte-carpet.js";
4
+ import { RUNTIME } from "../common/runtime.js";
5
+ import { RUNTIME_PROCESS_WORKER_BOOT_ENV, RUNTIME_PROCESS_WORKER_BOOT_VERSION, RUNTIME_PROCESS_WORKER_ENV, } from "../common/worker-runtime.js";
6
+ import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
7
+ import { toSharedBufferRegion, } from "../common/shared-buffer-region.js";
8
+ import { createBunConnectionPrimitives } from "../connections/bun.js";
9
+ import { createDenoConnectionPrimitives } from "../connections/deno.js";
10
+ import { FileDescriptor, ProcessSharedBuffer, } from "../connections/index.js";
11
+ import { createNodeConnectionPrimitives, loadNodeFutexAddon, } from "../connections/node.js";
12
+ import { loadNodeNativeAddon } from "../connections/node-addons.js";
13
+ import { detectPosixPlatform } from "../connections/posix.js";
14
+ const execFlagKey = (flag) => flag.split("=", 1)[0];
15
+ const NODE_PERMISSION_EXEC_FLAGS = new Set([
16
+ "--permission",
17
+ "--experimental-permission",
18
+ "--allow-fs-read",
19
+ "--allow-fs-write",
20
+ "--allow-worker",
21
+ "--allow-child-process",
22
+ "--allow-addons",
23
+ "--allow-wasi",
24
+ ]);
25
+ const NODE_WORKER_SAFE_EXEC_FLAGS = new Set([
26
+ "--experimental-transform-types",
27
+ "--expose-gc",
28
+ "--no-warnings",
29
+ ...NODE_PERMISSION_EXEC_FLAGS,
30
+ ]);
31
+ const isNodeWorkerSafeExecFlag = (flag) => NODE_WORKER_SAFE_EXEC_FLAGS.has(execFlagKey(flag));
32
+ const isNodePermissionExecFlag = (flag) => NODE_PERMISSION_EXEC_FLAGS.has(execFlagKey(flag));
33
+ export const toWorkerSafeExecArgv = (flags) => {
34
+ if (!flags || flags.length === 0)
35
+ return undefined;
36
+ const filtered = flags.filter(isNodeWorkerSafeExecFlag);
37
+ if (filtered.length === 0)
38
+ return undefined;
39
+ const seen = new Set();
40
+ const deduped = [];
41
+ for (const flag of filtered) {
42
+ if (seen.has(flag))
43
+ continue;
44
+ seen.add(flag);
45
+ deduped.push(flag);
46
+ }
47
+ return deduped;
48
+ };
49
+ export const toWorkerCompatExecArgv = (flags) => {
50
+ const safe = toWorkerSafeExecArgv(flags);
51
+ if (!safe || safe.length === 0)
52
+ return undefined;
53
+ const compat = safe.filter((flag) => !isNodePermissionExecFlag(flag));
54
+ return compat.length > 0 ? compat : undefined;
55
+ };
56
+ const toProcessSharedMemorySize = (byteLength) => {
57
+ if (!Number.isFinite(byteLength) || byteLength <= 0) {
58
+ throw new RangeError("process shared memory byteLength must be positive");
59
+ }
60
+ const size = Math.trunc(byteLength);
61
+ return size + ((64 - (size % 64)) % 64);
62
+ };
63
+ export const createProcessSharedMemoryAllocator = (debug) => {
64
+ if (RUNTIME !== "node")
65
+ return undefined;
66
+ let addon;
67
+ try {
68
+ const nodeModule = getNodeBuiltinModule("node:module");
69
+ if (nodeModule === undefined)
70
+ return undefined;
71
+ const require = nodeModule.createRequire(import.meta.url);
72
+ addon = loadNodeNativeAddon(require, "knitting_shared_memory");
73
+ }
74
+ catch (error) {
75
+ if (debug?.extras === true) {
76
+ console.warn("Process-shared memory allocator unavailable; falling back to SharedArrayBuffer.", error);
77
+ }
78
+ return undefined;
79
+ }
80
+ const backings = [];
81
+ return {
82
+ backings,
83
+ createBuffer: (byteLength) => {
84
+ const mapping = addon.createSharedMemory(toProcessSharedMemorySize(byteLength));
85
+ backings.push({
86
+ ...mapping,
87
+ runtime: "node",
88
+ buffer: mapping.sab,
89
+ kind: "shared-array-buffer",
90
+ byteLength: mapping.sab.byteLength,
91
+ });
92
+ return mapping.sab;
93
+ },
94
+ };
95
+ };
96
+ const PROCESS_WORKER_CHILD_FD = 0;
97
+ const DEFAULT_BUN_BINARY = "bun";
98
+ const DEFAULT_DENO_BINARY = "deno";
99
+ const DEFAULT_NODE_BINARY = "node";
100
+ const DENO_PROCESS_WORKER_BOOT_ENV_ALLOW = [
101
+ RUNTIME_PROCESS_WORKER_ENV,
102
+ RUNTIME_PROCESS_WORKER_BOOT_ENV,
103
+ ].join(",");
104
+ const DENO_PROCESS_WORKER_INTERNAL_FLAGS = [
105
+ `--allow-env=${DENO_PROCESS_WORKER_BOOT_ENV_ALLOW}`,
106
+ "--allow-ffi",
107
+ ];
108
+ const NODE_PROCESS_WORKER_EXEC_ARGV = [
109
+ "--no-warnings",
110
+ "--experimental-transform-types",
111
+ ];
112
+ const getProcessWorkerSharedMemoryPrimitives = () => {
113
+ switch (RUNTIME) {
114
+ case "bun":
115
+ return createBunConnectionPrimitives();
116
+ case "deno":
117
+ return createDenoConnectionPrimitives();
118
+ case "node":
119
+ return createNodeConnectionPrimitives();
120
+ default:
121
+ throw new Error("process worker runtime needs Node, Deno, or Bun shared memory primitives");
122
+ }
123
+ };
124
+ const processWorkerNeedsInheritedFd = (descriptor) => descriptor.name === undefined;
125
+ const isWindowsRuntimeHost = () => {
126
+ const denoOs = globalThis.Deno?.build?.os;
127
+ if (denoOs !== undefined)
128
+ return denoOs === "windows";
129
+ return globalThis.process?.platform === "win32";
130
+ };
131
+ let processWorkerMemoryNameCounter = 0;
132
+ const makeProcessWorkerMemoryName = (thread, prefix = "kpw") => {
133
+ const processId = globalThis.process?.pid ??
134
+ globalThis.Deno?.pid ??
135
+ 0;
136
+ const next = processWorkerMemoryNameCounter++;
137
+ // Keep total ≤ 30 chars: macOS POSIX shm_open limit is 31 including the leading /.
138
+ const pidTag = Math.abs(processId).toString(36).slice(-4);
139
+ const threadTag = (thread % 4096).toString(36);
140
+ const nextTag = (next % 1296).toString(36);
141
+ const randomTag = Math.random().toString(36).slice(2, 7);
142
+ const safePrefix = (prefix.replace(/[^a-z0-9_-]/gi, "_") || "kpw").slice(0, 8);
143
+ return `${safePrefix}_${pidTag}_${threadTag}_${nextTag}_${randomTag}`;
144
+ };
145
+ export const createProcessWorkerMemoryLayout = ({ signalBytes, abortBytes, payloadBytes, thread, sharedMemory, }) => {
146
+ const carpet = createByteCarpet();
147
+ const signalsSlice = carpet.take("signals", signalBytes);
148
+ const requestLockSlice = carpet.take("requestLockSector", LOCK_SECTOR_BYTE_LENGTH);
149
+ const returnLockSlice = carpet.take("returnLockSector", LOCK_SECTOR_BYTE_LENGTH);
150
+ const requestHeadersSlice = carpet.take("requestHeaders", getHeaderBlockByteLength({
151
+ slotCount: LockBound.slots,
152
+ slotStrideU32: HEADER_SLOT_STRIDE_U32,
153
+ alignTo: 64,
154
+ }));
155
+ const returnHeadersSlice = carpet.take("returnHeaders", getHeaderBlockByteLength({
156
+ slotCount: LockBound.slots,
157
+ slotStrideU32: HEADER_SLOT_STRIDE_U32,
158
+ alignTo: 64,
159
+ }));
160
+ const abortSignalsSlice = carpet.take("abortSignals", abortBytes);
161
+ const requestPayloadSlice = carpet.take("requestPayload", payloadBytes);
162
+ const returnPayloadSlice = carpet.take("returnPayload", payloadBytes);
163
+ const primitives = getProcessWorkerSharedMemoryPrimitives();
164
+ const forceNamed = sharedMemory.mode === "named" || isWindowsRuntimeHost();
165
+ const mapping = primitives.createSharedMemory(forceNamed
166
+ ? {
167
+ size: carpet.byteLength(),
168
+ mode: "create",
169
+ name: makeProcessWorkerMemoryName(thread, sharedMemory.namePrefix),
170
+ }
171
+ : { size: carpet.byteLength() });
172
+ const descriptor = FileDescriptor.fromMapping(mapping);
173
+ if (isWindowsRuntimeHost() && descriptor.name === undefined) {
174
+ throw new Error("Windows process worker shared memory must use a named mapping");
175
+ }
176
+ if (sharedMemory.mode === "named" && descriptor.name === undefined) {
177
+ throw new Error("processSharedMemory mode named needs a named shared-memory backend");
178
+ }
179
+ const buffer = mapping.buffer;
180
+ const bind = (slice) => makeSharedBufferRegion(buffer, slice.byteOffset, slice.byteLength);
181
+ const controlLayout = {
182
+ controlSAB: buffer,
183
+ signals: bind(signalsSlice),
184
+ abortSignals: bind(abortSignalsSlice),
185
+ lock: {
186
+ headers: bind(requestHeadersSlice),
187
+ headerSlotStrideU32: HEADER_SLOT_STRIDE_U32,
188
+ lockSector: bind(requestLockSlice),
189
+ payloadSector: bind(requestLockSlice),
190
+ },
191
+ returnLock: {
192
+ headers: bind(returnHeadersSlice),
193
+ headerSlotStrideU32: HEADER_SLOT_STRIDE_U32,
194
+ lockSector: bind(returnLockSlice),
195
+ payloadSector: bind(returnLockSlice),
196
+ },
197
+ slices: carpet.slices,
198
+ };
199
+ return {
200
+ mapping,
201
+ descriptor,
202
+ controlLayout,
203
+ lockPayload: bind(requestPayloadSlice),
204
+ returnPayload: bind(returnPayloadSlice),
205
+ cleanup: () => {
206
+ const name = descriptor.name;
207
+ if (name !== undefined && sharedMemory.unlinkOnShutdown) {
208
+ primitives.unlinkSharedMemory?.(name);
209
+ }
210
+ },
211
+ };
212
+ };
213
+ const toChildProcessSharedBufferMetadata = (source, descriptor) => {
214
+ const region = toSharedBufferRegion(source);
215
+ const metadata = descriptor.toMetadata();
216
+ const childMetadata = processWorkerNeedsInheritedFd(descriptor)
217
+ ? { ...metadata, fd: PROCESS_WORKER_CHILD_FD }
218
+ : metadata;
219
+ return ProcessSharedBuffer.fromDescriptor(new FileDescriptor(childMetadata), {
220
+ byteOffset: region.byteOffset,
221
+ byteLength: region.byteLength,
222
+ }).toMetadata();
223
+ };
224
+ const toProcessWorkerWireLockBuffers = (lock, descriptor) => ({
225
+ ...lock,
226
+ headers: toChildProcessSharedBufferMetadata(lock.headers, descriptor),
227
+ lockSector: toChildProcessSharedBufferMetadata(lock.lockSector, descriptor),
228
+ payload: toChildProcessSharedBufferMetadata(lock.payload, descriptor),
229
+ payloadSector: toChildProcessSharedBufferMetadata(lock.payloadSector, descriptor),
230
+ });
231
+ const isPlainRecord = (value) => {
232
+ if (value === null || typeof value !== "object")
233
+ return false;
234
+ const prototype = Object.getPrototypeOf(value);
235
+ return prototype === Object.prototype || prototype === null;
236
+ };
237
+ const serializeWorkerBootstrapValue = (value, seen = new WeakMap()) => {
238
+ if (value instanceof ProcessSharedBuffer)
239
+ return value.toMetadata();
240
+ if (value === null || typeof value !== "object")
241
+ return value;
242
+ const existing = seen.get(value);
243
+ if (existing !== undefined)
244
+ return existing;
245
+ if (Array.isArray(value)) {
246
+ const out = [];
247
+ seen.set(value, out);
248
+ for (const item of value) {
249
+ out.push(serializeWorkerBootstrapValue(item, seen));
250
+ }
251
+ return out;
252
+ }
253
+ if (!isPlainRecord(value))
254
+ return value;
255
+ const out = {};
256
+ seen.set(value, out);
257
+ for (const [key, item] of Object.entries(value)) {
258
+ out[key] = serializeWorkerBootstrapValue(item, seen);
259
+ }
260
+ return out;
261
+ };
262
+ export const serializeWorkerBootstrapData = (options) => {
263
+ const bootstrap = options.bootstrap;
264
+ if (bootstrap === undefined || bootstrap.data === undefined)
265
+ return options;
266
+ return {
267
+ ...options,
268
+ bootstrap: {
269
+ ...bootstrap,
270
+ data: serializeWorkerBootstrapValue(bootstrap.data),
271
+ },
272
+ };
273
+ };
274
+ export const toProcessWorkerBootPayload = (workerData, memory) => ({
275
+ version: RUNTIME_PROCESS_WORKER_BOOT_VERSION,
276
+ workerData: {
277
+ ...workerData,
278
+ sab: toChildProcessSharedBufferMetadata(workerData.sab, memory.descriptor),
279
+ abortSignalSAB: workerData.abortSignalSAB === undefined
280
+ ? undefined
281
+ : toChildProcessSharedBufferMetadata(workerData.abortSignalSAB, memory.descriptor),
282
+ lock: toProcessWorkerWireLockBuffers(workerData.lock, memory.descriptor),
283
+ returnLock: toProcessWorkerWireLockBuffers(workerData.returnLock, memory.descriptor),
284
+ },
285
+ });
286
+ const toProcessWorkerPath = (specifier) => {
287
+ const value = specifier instanceof URL ? specifier.href : specifier;
288
+ if (value.startsWith("file:"))
289
+ return fileURLToPathCompat(value);
290
+ return value;
291
+ };
292
+ export const readProcessWorkerRuntime = (options) => {
293
+ const runtime = options?.processRuntime ?? "deno";
294
+ if (runtime === "bun" || runtime === "deno" || runtime === "node") {
295
+ return runtime;
296
+ }
297
+ throw new TypeError(`Unsupported process worker runtime: ${String(runtime)}`);
298
+ };
299
+ export const readProcessWorkerCommandPrefix = (options) => {
300
+ const prefix = options?.processCommandPrefix;
301
+ if (prefix === undefined)
302
+ return undefined;
303
+ if (!Array.isArray(prefix)) {
304
+ throw new TypeError("processCommandPrefix must be an argv array");
305
+ }
306
+ if (prefix.length === 0)
307
+ return undefined;
308
+ const out = [];
309
+ for (const [index, value] of prefix.entries()) {
310
+ if (typeof value !== "string" || value.length === 0) {
311
+ throw new TypeError(`processCommandPrefix[${index}] must be a non-empty string`);
312
+ }
313
+ out.push(value);
314
+ }
315
+ return out;
316
+ };
317
+ const readProcessSharedMemoryMode = (value) => {
318
+ if (value === undefined)
319
+ return "inherit";
320
+ if (value === "inherit" || value === "named")
321
+ return value;
322
+ throw new TypeError(`Unsupported processSharedMemory mode: ${String(value)}`);
323
+ };
324
+ export const readProcessSharedMemorySettings = (options) => {
325
+ const input = options?.processSharedMemory;
326
+ if (input === undefined || typeof input === "string") {
327
+ return {
328
+ mode: readProcessSharedMemoryMode(input),
329
+ unlinkOnShutdown: true,
330
+ };
331
+ }
332
+ if (typeof input !== "object" || input === null) {
333
+ throw new TypeError("processSharedMemory must be a mode or options object");
334
+ }
335
+ const settings = input;
336
+ const mode = readProcessSharedMemoryMode(settings.mode);
337
+ const out = {
338
+ mode,
339
+ unlinkOnShutdown: settings.unlinkOnShutdown !== false,
340
+ };
341
+ if (settings.namePrefix !== undefined) {
342
+ if (typeof settings.namePrefix !== "string" ||
343
+ settings.namePrefix.length === 0 ||
344
+ settings.namePrefix.includes("\0")) {
345
+ throw new TypeError("processSharedMemory.namePrefix must be a non-empty string without NUL bytes");
346
+ }
347
+ out.namePrefix = settings.namePrefix;
348
+ }
349
+ return out;
350
+ };
351
+ const currentProcessEnv = () => ({
352
+ ...getNodeProcess()?.env,
353
+ });
354
+ const processWorkerEnv = (extra) => ({
355
+ ...currentProcessEnv(),
356
+ [RUNTIME_PROCESS_WORKER_ENV]: "1",
357
+ ...extra,
358
+ });
359
+ const processWorkerBootEnv = (bootPayload) => processWorkerEnv({
360
+ [RUNTIME_PROCESS_WORKER_BOOT_ENV]: JSON.stringify(bootPayload),
361
+ });
362
+ const stringProcessEnv = (input) => {
363
+ const out = {};
364
+ for (const [key, value] of Object.entries(input)) {
365
+ if (value !== undefined)
366
+ out[key] = value;
367
+ }
368
+ return out;
369
+ };
370
+ const processWorkerBunBinary = (bun, commandPrefix) => getNodeProcess()?.env?.BUN_BINARY ??
371
+ (commandPrefix === undefined ? bun?.argv?.[0] : undefined) ??
372
+ DEFAULT_BUN_BINARY;
373
+ const processWorkerDenoBinary = (deno, commandPrefix) => getNodeProcess()?.env?.DENO_BINARY ??
374
+ (commandPrefix === undefined ? deno?.execPath?.() : undefined) ??
375
+ DEFAULT_DENO_BINARY;
376
+ const processWorkerDenoFlags = (permission) => {
377
+ if (permission?.enabled !== true || permission.unsafe === true) {
378
+ return ["-A"];
379
+ }
380
+ return [
381
+ ...DENO_PROCESS_WORKER_INTERNAL_FLAGS,
382
+ ...permission.deno.flags,
383
+ ];
384
+ };
385
+ const processWorkerNodeBinary = (commandPrefix) => {
386
+ const nodeProcess = getNodeProcess();
387
+ return nodeProcess?.env?.NODE_BINARY ??
388
+ (commandPrefix === undefined && RUNTIME === "node"
389
+ ? nodeProcess?.execPath
390
+ : undefined) ??
391
+ DEFAULT_NODE_BINARY;
392
+ };
393
+ const processWorkerNodeExecArgv = () => {
394
+ const out = [];
395
+ const seen = new Set();
396
+ const add = (flag) => {
397
+ if (seen.has(flag))
398
+ return;
399
+ seen.add(flag);
400
+ out.push(flag);
401
+ };
402
+ for (const flag of toWorkerCompatExecArgv(getNodeProcess()?.execArgv) ?? []) {
403
+ add(flag);
404
+ }
405
+ for (const flag of NODE_PROCESS_WORKER_EXEC_ARGV)
406
+ add(flag);
407
+ return out;
408
+ };
409
+ const processWorkerCommand = ({ processRuntime, workerUrl, bun, deno, commandPrefix, permission, }) => {
410
+ const workerPath = toProcessWorkerPath(workerUrl);
411
+ let command;
412
+ if (processRuntime === "deno") {
413
+ command = [
414
+ processWorkerDenoBinary(deno, commandPrefix),
415
+ "run",
416
+ ...processWorkerDenoFlags(permission),
417
+ workerPath,
418
+ ];
419
+ }
420
+ else if (processRuntime === "node") {
421
+ command = [
422
+ processWorkerNodeBinary(commandPrefix),
423
+ ...processWorkerNodeExecArgv(),
424
+ workerPath,
425
+ ];
426
+ }
427
+ else {
428
+ command = [processWorkerBunBinary(bun, commandPrefix), workerPath];
429
+ }
430
+ return commandPrefix === undefined
431
+ ? command
432
+ : [...commandPrefix, ...command];
433
+ };
434
+ export const createProcessWorkerNativeSignalNotifier = ({ processRuntime, signal, }) => {
435
+ if (RUNTIME !== "node" || processRuntime !== "node")
436
+ return undefined;
437
+ try {
438
+ const futex = loadNodeFutexAddon();
439
+ return () => {
440
+ futex.wakeU32(signal.buffer, signal.byteOffset, 1);
441
+ };
442
+ }
443
+ catch {
444
+ return undefined;
445
+ }
446
+ };
447
+ const createProcessWorkerEventHub = () => {
448
+ const messageHandlers = [];
449
+ const errorHandlers = [];
450
+ const exitHandlers = [];
451
+ return {
452
+ emitMessage: (message) => {
453
+ for (const handler of messageHandlers)
454
+ handler(message);
455
+ },
456
+ emitError: (error) => {
457
+ for (const handler of errorHandlers)
458
+ handler(error);
459
+ },
460
+ emitExit: (code) => {
461
+ for (const handler of exitHandlers)
462
+ handler(code);
463
+ },
464
+ on: (event, listener) => {
465
+ if (event === "message")
466
+ messageHandlers.push(listener);
467
+ if (event === "error")
468
+ errorHandlers.push(listener);
469
+ if (event === "exit")
470
+ exitHandlers.push(listener);
471
+ },
472
+ };
473
+ };
474
+ const spawnBunHostedProcessWorker = ({ workerUrl, bootPayload, memory, processRuntime, commandPrefix, permission, }) => {
475
+ const bun = globalThis.Bun;
476
+ if (typeof bun?.spawn !== "function") {
477
+ throw new Error("Bun.spawn is not available for process workers");
478
+ }
479
+ const events = createProcessWorkerEventHub();
480
+ const nodeProcess = getNodeProcess();
481
+ const useIpcBoot = processRuntime === "bun" && commandPrefix === undefined;
482
+ const spawnOptions = {
483
+ cmd: processWorkerCommand({
484
+ processRuntime,
485
+ workerUrl,
486
+ bun,
487
+ commandPrefix,
488
+ permission,
489
+ }),
490
+ cwd: nodeProcess?.cwd?.(),
491
+ env: useIpcBoot ? processWorkerEnv() : processWorkerBootEnv(bootPayload),
492
+ stdin: processWorkerNeedsInheritedFd(memory.descriptor)
493
+ ? memory.mapping.fd
494
+ : "ignore",
495
+ stdout: "inherit",
496
+ stderr: "inherit",
497
+ onExit: (_subprocess, exitCode, _signalCode, error) => {
498
+ if (error !== undefined)
499
+ events.emitError(error);
500
+ events.emitExit(exitCode ?? -1);
501
+ },
502
+ };
503
+ if (useIpcBoot) {
504
+ spawnOptions.serialization = "advanced";
505
+ spawnOptions.ipc = (message) => {
506
+ events.emitMessage(message);
507
+ };
508
+ }
509
+ const child = bun.spawn(spawnOptions);
510
+ if (useIpcBoot) {
511
+ queueMicrotask(() => child.send?.(bootPayload));
512
+ }
513
+ child.exited.catch((error) => {
514
+ events.emitError(error);
515
+ });
516
+ return {
517
+ terminate: () => {
518
+ child.kill();
519
+ return child.exited.catch(() => undefined);
520
+ },
521
+ on: events.on,
522
+ };
523
+ };
524
+ const spawnNodeHostedProcessWorker = ({ workerUrl, bootPayload, memory, processRuntime, commandPrefix, permission, }) => {
525
+ const childProcess = getNodeBuiltinModule("node:child_process");
526
+ if (typeof childProcess?.spawn !== "function") {
527
+ throw new Error("node:child_process.spawn is not available");
528
+ }
529
+ const events = createProcessWorkerEventHub();
530
+ const useIpcBoot = (processRuntime === "bun" || processRuntime === "node") &&
531
+ commandPrefix === undefined;
532
+ const [command, ...args] = processWorkerCommand({
533
+ processRuntime,
534
+ workerUrl,
535
+ commandPrefix,
536
+ permission,
537
+ });
538
+ const child = childProcess.spawn(command, args, {
539
+ cwd: getNodeProcess()?.cwd?.(),
540
+ env: useIpcBoot ? processWorkerEnv() : processWorkerBootEnv(bootPayload),
541
+ stdio: useIpcBoot
542
+ ? [
543
+ processWorkerNeedsInheritedFd(memory.descriptor)
544
+ ? memory.mapping.fd
545
+ : "ignore",
546
+ "inherit",
547
+ "inherit",
548
+ "ipc",
549
+ ]
550
+ : [
551
+ processWorkerNeedsInheritedFd(memory.descriptor)
552
+ ? memory.mapping.fd
553
+ : "ignore",
554
+ "inherit",
555
+ "inherit",
556
+ ],
557
+ });
558
+ if (useIpcBoot) {
559
+ child.on("message", events.emitMessage);
560
+ queueMicrotask(() => child.send?.(bootPayload));
561
+ }
562
+ child.on("error", events.emitError);
563
+ child.on("exit", (code) => events.emitExit(code ?? -1));
564
+ return {
565
+ terminate: () => child.kill(),
566
+ unref: () => child.unref?.(),
567
+ on: events.on,
568
+ };
569
+ };
570
+ const getDenoRuntime = () => globalThis.Deno;
571
+ const denoFileRid = (file) => {
572
+ for (const symbol of Object.getOwnPropertySymbols(file)) {
573
+ if (String(symbol) === "Symbol(Deno.internal.rid)") {
574
+ const rid = file[symbol];
575
+ if (typeof rid === "number")
576
+ return rid;
577
+ }
578
+ }
579
+ throw new Error("Deno FsFile resource id is not available");
580
+ };
581
+ const openDenoInheritedFd = (fd) => {
582
+ const deno = getDenoRuntime();
583
+ if (typeof deno?.openSync !== "function") {
584
+ throw new Error("Deno.openSync is not available for process workers");
585
+ }
586
+ const fdPath = detectPosixPlatform() === "linux"
587
+ ? `/proc/self/fd/${fd}`
588
+ : `/dev/fd/${fd}`;
589
+ return deno.openSync(fdPath, { read: true, write: true });
590
+ };
591
+ const spawnDenoHostedProcessWorker = ({ workerUrl, bootPayload, memory, processRuntime, commandPrefix, permission, }) => {
592
+ const deno = getDenoRuntime();
593
+ if (typeof deno?.Command !== "function") {
594
+ throw new Error("Deno.Command is not available for process workers");
595
+ }
596
+ const inheritedFd = processWorkerNeedsInheritedFd(memory.descriptor)
597
+ ? openDenoInheritedFd(memory.mapping.fd)
598
+ : undefined;
599
+ const events = createProcessWorkerEventHub();
600
+ const [command, ...args] = processWorkerCommand({
601
+ processRuntime,
602
+ workerUrl,
603
+ deno,
604
+ commandPrefix,
605
+ permission,
606
+ });
607
+ const child = new deno.Command(command, {
608
+ args,
609
+ cwd: deno.cwd?.(),
610
+ env: stringProcessEnv(processWorkerBootEnv(bootPayload)),
611
+ stdin: inheritedFd === undefined ? "null" : denoFileRid(inheritedFd),
612
+ stdout: "inherit",
613
+ stderr: "inherit",
614
+ }).spawn();
615
+ const closeInheritedFd = () => {
616
+ try {
617
+ inheritedFd?.close?.();
618
+ }
619
+ catch {
620
+ }
621
+ };
622
+ child.status.then((status) => {
623
+ closeInheritedFd();
624
+ events.emitExit(status.code);
625
+ }, (error) => {
626
+ closeInheritedFd();
627
+ events.emitError(error);
628
+ events.emitExit(-1);
629
+ });
630
+ return {
631
+ terminate: () => {
632
+ try {
633
+ child.kill("SIGTERM");
634
+ }
635
+ catch {
636
+ }
637
+ return child.status.finally(closeInheritedFd);
638
+ },
639
+ on: events.on,
640
+ };
641
+ };
642
+ export const spawnProcessWorker = (options) => {
643
+ switch (RUNTIME) {
644
+ case "bun":
645
+ return spawnBunHostedProcessWorker(options);
646
+ case "node":
647
+ return spawnNodeHostedProcessWorker(options);
648
+ case "deno":
649
+ return spawnDenoHostedProcessWorker(options);
650
+ default:
651
+ throw new Error("process worker runtime is only available in Node, Deno, or Bun");
652
+ }
653
+ };
654
+ export const terminateWorkerQuietly = (worker) => {
655
+ try {
656
+ // Runaway worker termination can be slow or stuck on some runtimes; once the
657
+ // pool is closing it must not keep the host process alive.
658
+ worker.unref?.();
659
+ void Promise.resolve(worker.terminate()).catch(() => { });
660
+ }
661
+ catch {
662
+ }
663
+ };
664
+ export const cleanupProcessWorkerMemoryQuietly = (memory) => {
665
+ try {
666
+ memory?.cleanup();
667
+ }
668
+ catch {
669
+ }
670
+ };
@@ -9,7 +9,7 @@ type CreateHostTxQueueArgs = {
9
9
  max?: number;
10
10
  lock: Lock2;
11
11
  returnLock: Lock2;
12
- abortSignals?: Pick<SignalAbortStore, "getSignal" | "resetSignal" | "closeNow">;
12
+ abortSignals?: Pick<SignalAbortStore, "getSignal" | "setSignal" | "resetSignal" | "closeNow">;
13
13
  now?: () => number;
14
14
  };
15
15
  export declare function createHostTxQueue({ max, lock, returnLock, abortSignals, now, }: CreateHostTxQueueArgs): {
@@ -98,7 +98,9 @@ export function createHostTxQueue({ max, lock, returnLock, abortSignals, now, })
98
98
  if (maybeSignal === abortSignals.closeNow) {
99
99
  return Promise.reject(AbortSignalPoolExhausted);
100
100
  }
101
- new OneShotDeferred(deferred, () => resetSignal(maybeSignal));
101
+ new OneShotDeferred(deferred, () => resetSignal(maybeSignal), () => {
102
+ abortSignals.setSignal(maybeSignal);
103
+ });
102
104
  const encodedSignalMeta = ((maybeSignal + ABORT_SIGNAL_META_OFFSET) & FUNCTION_META_MASK) >>> 0;
103
105
  slot[TaskIndex.FunctionID] =
104
106
  ((encodedSignalMeta << FUNCTION_META_SHIFT) | functionIDMasked) >>> 0;
@@ -19,5 +19,5 @@ export declare const signalAbortFactory: ({ sab, maxSignals, }: {
19
19
  };
20
20
  export declare class OneShotDeferred<T> {
21
21
  #private;
22
- constructor(deferred: ReturnType<typeof withResolvers<T>>, onSettle: () => void);
22
+ constructor(deferred: ReturnType<typeof withResolvers<T>>, onSettle: () => void, onEmptyReject?: () => void);
23
23
  }