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
|
@@ -111,7 +111,7 @@ export const signalAbortFactory = ({ sab, maxSignals, }) => {
|
|
|
111
111
|
};
|
|
112
112
|
export class OneShotDeferred {
|
|
113
113
|
#triggered = false;
|
|
114
|
-
constructor(deferred, onSettle) {
|
|
114
|
+
constructor(deferred, onSettle, onEmptyReject) {
|
|
115
115
|
const settleOnce = (fn) => (...args) => {
|
|
116
116
|
if (this.#triggered)
|
|
117
117
|
return;
|
|
@@ -121,6 +121,14 @@ export class OneShotDeferred {
|
|
|
121
121
|
};
|
|
122
122
|
deferred.resolve = settleOnce(deferred.resolve);
|
|
123
123
|
deferred.reject = settleOnce(deferred.reject);
|
|
124
|
-
deferred.promise.reject =
|
|
124
|
+
deferred.promise.reject = (reason) => {
|
|
125
|
+
if (this.#triggered)
|
|
126
|
+
return;
|
|
127
|
+
if (reason === undefined && onEmptyReject !== undefined) {
|
|
128
|
+
onEmptyReject();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
deferred.reject(reason);
|
|
132
|
+
};
|
|
125
133
|
}
|
|
126
134
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -164,8 +164,34 @@ type DebugOptions = {
|
|
|
164
164
|
logHref?: boolean;
|
|
165
165
|
logImportedUrl?: boolean;
|
|
166
166
|
};
|
|
167
|
+
type WorkerBootstrapContext = {
|
|
168
|
+
readonly thread: number;
|
|
169
|
+
readonly totalNumberOfThread: number;
|
|
170
|
+
readonly runtime: "node" | "deno" | "bun" | "unknown";
|
|
171
|
+
};
|
|
172
|
+
type WorkerBootstrapFunction<Data = unknown> = (data: Data, context: WorkerBootstrapContext) => MaybePromise<void>;
|
|
173
|
+
type WorkerBootstrapOptions<Data = unknown> = {
|
|
174
|
+
/**
|
|
175
|
+
* Module imported inside the worker before task modules are imported.
|
|
176
|
+
* Relative paths are resolved from the `createPool(...)` caller.
|
|
177
|
+
*/
|
|
178
|
+
href: string;
|
|
179
|
+
/**
|
|
180
|
+
* Exported bootstrap function name. Defaults to `"default"`.
|
|
181
|
+
*/
|
|
182
|
+
name?: string;
|
|
183
|
+
/**
|
|
184
|
+
* Structured data passed to the bootstrap function.
|
|
185
|
+
*/
|
|
186
|
+
data?: Data;
|
|
187
|
+
};
|
|
167
188
|
type WorkerSettings = {
|
|
168
189
|
resolveAfterFinishingAll?: true;
|
|
190
|
+
/**
|
|
191
|
+
* Privileged async worker hook that runs once before task modules import.
|
|
192
|
+
* Use it to shape the worker environment before user task code loads.
|
|
193
|
+
*/
|
|
194
|
+
bootstrap?: WorkerBootstrapOptions;
|
|
169
195
|
/**
|
|
170
196
|
* Experimental worker runtime.
|
|
171
197
|
* "thread" uses Worker/worker_threads. "process" spawns another JavaScript
|
|
@@ -173,17 +199,31 @@ type WorkerSettings = {
|
|
|
173
199
|
*/
|
|
174
200
|
runtime?: "thread" | "process";
|
|
175
201
|
/**
|
|
176
|
-
* Runtime executable to use when runtime is "process". Defaults to "
|
|
202
|
+
* Runtime executable to use when runtime is "process". Defaults to "deno".
|
|
177
203
|
*/
|
|
178
204
|
processRuntime?: "bun" | "deno" | "node";
|
|
179
205
|
/**
|
|
180
206
|
* Command argv to prepend before the process worker runtime command.
|
|
181
|
-
* Useful for wrappers such as systemd-run, cgexec, nice, or
|
|
207
|
+
* Useful for wrappers such as systemd-run, cgexec, nice, taskset, or
|
|
208
|
+
* docker. Knitting appends the runtime command after this prefix.
|
|
182
209
|
*
|
|
183
210
|
* Example:
|
|
184
211
|
* ["systemd-run", "--scope", "-p", "MemoryMax=500M", "-p", "CPUQuota=25%"]
|
|
212
|
+
*
|
|
213
|
+
* With containers, use processSharedMemory: "named", share the IPC
|
|
214
|
+
* namespace, mount the worker files at the same path, and forward
|
|
215
|
+
* KNITTING_PROCESS_WORKER plus KNITTING_PROCESS_WORKER_BOOT.
|
|
185
216
|
*/
|
|
186
217
|
processCommandPrefix?: string[];
|
|
218
|
+
/**
|
|
219
|
+
* How process workers discover their shared-memory control channel.
|
|
220
|
+
*
|
|
221
|
+
* "inherit" keeps the POSIX fd-inheritance path and is the default outside
|
|
222
|
+
* Windows. "named" creates an OS-named shared-memory object that wrappers
|
|
223
|
+
* such as containers can reopen by name when they share the same IPC
|
|
224
|
+
* namespace.
|
|
225
|
+
*/
|
|
226
|
+
processSharedMemory?: ProcessSharedMemoryMode | ProcessSharedMemorySettings;
|
|
187
227
|
timers?: WorkerTimers;
|
|
188
228
|
/**
|
|
189
229
|
* Hard task execution timeout in milliseconds.
|
|
@@ -191,6 +231,12 @@ type WorkerSettings = {
|
|
|
191
231
|
*/
|
|
192
232
|
hardTimeoutMs?: number;
|
|
193
233
|
};
|
|
234
|
+
type ProcessSharedMemoryMode = "inherit" | "named";
|
|
235
|
+
type ProcessSharedMemorySettings = {
|
|
236
|
+
mode?: ProcessSharedMemoryMode;
|
|
237
|
+
namePrefix?: string;
|
|
238
|
+
unlinkOnShutdown?: boolean;
|
|
239
|
+
};
|
|
194
240
|
type WorkerTimers = {
|
|
195
241
|
/**
|
|
196
242
|
* Busy-spin budget before parking (microseconds).
|
|
@@ -277,7 +323,7 @@ type CreatePool = {
|
|
|
277
323
|
debug?: DebugOptions;
|
|
278
324
|
source?: string;
|
|
279
325
|
};
|
|
280
|
-
export type { WorkerCall as WorkerCall, WorkerInvoke as WorkerInvoke, WorkerContext as WorkerContext, CreateContext as CreateContext, WorkerData as WorkerData, LockBuffers as LockBuffers, ValidInput as ValidInput, Args as Args, MaybePromise as MaybePromise, TaskInput as TaskInput, TaskTimeout as TaskTimeout, TaskFn as TaskFn, AbortSignalConfig as AbortSignalConfig, AbortSignalOption as AbortSignalOption, AbortSignalMethods as AbortSignalMethods, AbortSignalToolkit as AbortSignalToolkit, Composed as Composed, tasks as tasks, ComposedWithKey as ComposedWithKey, FunctionMapType as FunctionMapType, FixPoint as FixPoint, ImportTaskOptions as ImportTaskOptions, SecondPart as SecondPart, SingleTaskPool as SingleTaskPool, Pool as Pool, ReturnFixed as ReturnFixed, External as External, Inliner as Inliner, BalancerStrategy as BalancerStrategy, Balancer as Balancer, DebugOptions as DebugOptions, WorkerSettings as WorkerSettings, WorkerTimers as WorkerTimers, DispatcherSettings as DispatcherSettings, CreatePool as CreatePool, PayloadBufferMode as PayloadBufferMode, PayloadBufferOptions as PayloadBufferOptions, PermissionProtocol as PermissionProtocol, PermissionProtocolInput as PermissionProtocolInput, ResolvedPermissionProtocol as ResolvedPermissionProtocol, Envelope as Envelope, SharedBufferTextCompat as SharedBufferTextCompat, LockBufferTextCompat as LockBufferTextCompat, SharedBufferRegion as SharedBufferRegion, SharedBufferSource as SharedBufferSource, };
|
|
326
|
+
export type { WorkerCall as WorkerCall, WorkerInvoke as WorkerInvoke, WorkerContext as WorkerContext, CreateContext as CreateContext, WorkerData as WorkerData, LockBuffers as LockBuffers, ValidInput as ValidInput, Args as Args, MaybePromise as MaybePromise, TaskInput as TaskInput, TaskTimeout as TaskTimeout, TaskFn as TaskFn, AbortSignalConfig as AbortSignalConfig, AbortSignalOption as AbortSignalOption, AbortSignalMethods as AbortSignalMethods, AbortSignalToolkit as AbortSignalToolkit, Composed as Composed, tasks as tasks, ComposedWithKey as ComposedWithKey, FunctionMapType as FunctionMapType, FixPoint as FixPoint, ImportTaskOptions as ImportTaskOptions, SecondPart as SecondPart, SingleTaskPool as SingleTaskPool, Pool as Pool, ReturnFixed as ReturnFixed, External as External, Inliner as Inliner, BalancerStrategy as BalancerStrategy, Balancer as Balancer, DebugOptions as DebugOptions, WorkerBootstrapContext as WorkerBootstrapContext, WorkerBootstrapFunction as WorkerBootstrapFunction, WorkerBootstrapOptions as WorkerBootstrapOptions, WorkerSettings as WorkerSettings, ProcessSharedMemoryMode as ProcessSharedMemoryMode, ProcessSharedMemorySettings as ProcessSharedMemorySettings, WorkerTimers as WorkerTimers, DispatcherSettings as DispatcherSettings, CreatePool as CreatePool, PayloadBufferMode as PayloadBufferMode, PayloadBufferOptions as PayloadBufferOptions, PermissionProtocol as PermissionProtocol, PermissionProtocolInput as PermissionProtocolInput, ResolvedPermissionProtocol as ResolvedPermissionProtocol, Envelope as Envelope, SharedBufferTextCompat as SharedBufferTextCompat, LockBufferTextCompat as LockBufferTextCompat, SharedBufferRegion as SharedBufferRegion, SharedBufferSource as SharedBufferSource, };
|
|
281
327
|
export type { Task as Task } from "./memory/lock.js";
|
|
282
328
|
export { LockBound as LockBound, PayloadBuffer as PayloadBuffer, PayloadSignal as PayloadSignal, TaskIndex as TaskIndex, } from "./memory/lock.js";
|
|
283
329
|
export type { RegisterMalloc as RegisterMalloc } from "./memory/regionRegistry.js";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { RUNTIME } from "../common/runtime.js";
|
|
10
|
+
import { toModuleUrl } from "../common/module-url.js";
|
|
11
|
+
import { ProcessSharedBuffer } from "../connections/process-shared-buffer.js";
|
|
12
|
+
const DEFAULT_BOOTSTRAP_EXPORT_NAME = "default";
|
|
13
|
+
const isWorkerBootstrapOptions = (value) => {
|
|
14
|
+
if (!value || typeof value !== "object")
|
|
15
|
+
return false;
|
|
16
|
+
const candidate = value;
|
|
17
|
+
return typeof candidate.href === "string" &&
|
|
18
|
+
candidate.href.length > 0 &&
|
|
19
|
+
(candidate.name === undefined ||
|
|
20
|
+
(typeof candidate.name === "string" && candidate.name.length > 0));
|
|
21
|
+
};
|
|
22
|
+
const isProcessSharedBufferMetadata = (value) => {
|
|
23
|
+
try {
|
|
24
|
+
ProcessSharedBuffer.fromMetadata(value);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const reviveWorkerBootstrapValue = (value, seen = new WeakMap()) => {
|
|
32
|
+
if (isProcessSharedBufferMetadata(value)) {
|
|
33
|
+
return ProcessSharedBuffer.fromMetadata(value);
|
|
34
|
+
}
|
|
35
|
+
if (value === null || typeof value !== "object")
|
|
36
|
+
return value;
|
|
37
|
+
const existing = seen.get(value);
|
|
38
|
+
if (existing !== undefined)
|
|
39
|
+
return existing;
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
const out = [];
|
|
42
|
+
seen.set(value, out);
|
|
43
|
+
for (const item of value) {
|
|
44
|
+
out.push(reviveWorkerBootstrapValue(item, seen));
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
const prototype = Object.getPrototypeOf(value);
|
|
49
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
50
|
+
return value;
|
|
51
|
+
const out = {};
|
|
52
|
+
seen.set(value, out);
|
|
53
|
+
for (const [key, item] of Object.entries(value)) {
|
|
54
|
+
out[key] = reviveWorkerBootstrapValue(item, seen);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
};
|
|
58
|
+
export const runWorkerBootstrap = async ({ bootstrap, thread, totalNumberOfThread, }) => {
|
|
59
|
+
if (bootstrap === undefined)
|
|
60
|
+
return;
|
|
61
|
+
if (!isWorkerBootstrapOptions(bootstrap)) {
|
|
62
|
+
throw new TypeError("worker.bootstrap must include a non-empty href");
|
|
63
|
+
}
|
|
64
|
+
const module = await import(__rewriteRelativeImportExtension(toModuleUrl(bootstrap.href)));
|
|
65
|
+
const name = bootstrap.name ?? DEFAULT_BOOTSTRAP_EXPORT_NAME;
|
|
66
|
+
const selected = module[name];
|
|
67
|
+
if (typeof selected !== "function") {
|
|
68
|
+
const available = Object.keys(module).join(", ");
|
|
69
|
+
throw new TypeError(`worker.bootstrap expected export "${name}" from "${bootstrap.href}" to be a function.` +
|
|
70
|
+
` Available exports: ${available || "(none)"}`);
|
|
71
|
+
}
|
|
72
|
+
const context = Object.freeze({
|
|
73
|
+
thread,
|
|
74
|
+
totalNumberOfThread,
|
|
75
|
+
runtime: RUNTIME,
|
|
76
|
+
});
|
|
77
|
+
await selected(reviveWorkerBootstrapValue(bootstrap.data), context);
|
|
78
|
+
};
|
|
@@ -49,12 +49,6 @@ const readSignal = (slot) => {
|
|
|
49
49
|
const signal = (encodedSignal - ABORT_SIGNAL_META_OFFSET) | 0;
|
|
50
50
|
return signal >= 0 ? signal : NO_ABORT_SIGNAL;
|
|
51
51
|
};
|
|
52
|
-
const throwIfAborted = (signal, hasAborted) => {
|
|
53
|
-
if (signal === NO_ABORT_SIGNAL)
|
|
54
|
-
return;
|
|
55
|
-
if (hasAborted(signal))
|
|
56
|
-
throw new Error("Task aborted");
|
|
57
|
-
};
|
|
58
52
|
const makeToolkitCache = (hasAborted) => {
|
|
59
53
|
const bySignal = [];
|
|
60
54
|
return (signal) => {
|
|
@@ -86,7 +80,6 @@ export const composeWorkerRunner = ({ job, timeout, hasAborted, now, }) => {
|
|
|
86
80
|
if (!timeout) {
|
|
87
81
|
return (slot) => {
|
|
88
82
|
const signal = readSignal(slot);
|
|
89
|
-
throwIfAborted(signal, hasAborted);
|
|
90
83
|
if (signal === NO_ABORT_SIGNAL)
|
|
91
84
|
return job(slot.value);
|
|
92
85
|
return job(slot.value, getToolkit(signal));
|
|
@@ -94,7 +87,6 @@ export const composeWorkerRunner = ({ job, timeout, hasAborted, now, }) => {
|
|
|
94
87
|
}
|
|
95
88
|
return (slot) => {
|
|
96
89
|
const signal = readSignal(slot);
|
|
97
|
-
throwIfAborted(signal, hasAborted);
|
|
98
90
|
const result = signal === NO_ABORT_SIGNAL
|
|
99
91
|
? job(slot.value)
|
|
100
92
|
: job(slot.value, getToolkit(signal));
|
package/src/worker/loop.js
CHANGED
|
@@ -1,30 +1,18 @@
|
|
|
1
|
-
import { RUNTIME_IS_MAIN_THREAD, RUNTIME_IS_PROCESS_WORKER, RUNTIME_PARENT_PORT,
|
|
2
|
-
import { isSharedBufferSource
|
|
1
|
+
import { createRuntimeMessageChannel, RUNTIME_IS_MAIN_THREAD, RUNTIME_IS_PROCESS_WORKER, RUNTIME_PARENT_PORT, RUNTIME_WORKER_DATA, } from "../common/worker-runtime.js";
|
|
2
|
+
import { isSharedBufferSource } from "../common/shared-buffer-region.js";
|
|
3
3
|
import { isLockBufferTextCompat } from "../common/shared-buffer-text.js";
|
|
4
4
|
import { createWorkerRxQueue } from "./rx-queue.js";
|
|
5
|
-
import { createSharedMemoryTransport
|
|
5
|
+
import { createSharedMemoryTransport } from "../ipc/transport/shared-memory.js";
|
|
6
6
|
import { lock2 } from "../memory/lock.js";
|
|
7
7
|
import { getFunctions } from "./task-loader.js";
|
|
8
|
-
import { pauseGeneric, sleepUntilChanged, whilePausing
|
|
8
|
+
import { pauseGeneric, sleepUntilChanged, whilePausing } from "./timers.js";
|
|
9
9
|
import { RUNTIME, SET_IMMEDIATE } from "../common/runtime.js";
|
|
10
10
|
import { getNodeProcess } from "../common/node-compat.js";
|
|
11
|
-
import {
|
|
12
|
-
import { createBunConnectionPrimitives } from "../connections/bun.js";
|
|
13
|
-
import { createDenoConnectionPrimitives } from "../connections/deno.js";
|
|
14
|
-
import { loadNodeFutexAddon } from "../connections/node.js";
|
|
15
|
-
import { installTerminationGuard, installUnhandledRejectionSilencer, installPerformanceNowGuard, scrubWorkerDataSensitiveBuffers, assertWorkerSharedMemoryBootData, assertWorkerImportsResolved, } from "./safety/index.js";
|
|
11
|
+
import { assertWorkerImportsResolved, assertWorkerSharedMemoryBootData, installPerformanceNowGuard, installTerminationGuard, installUnhandledRejectionSilencer, scrubWorkerDataSensitiveBuffers, } from "./safety/index.js";
|
|
16
12
|
import { signalAbortFactory } from "../shared/abortSignal.js";
|
|
13
|
+
import { runWorkerBootstrap } from "./bootstrap.js";
|
|
14
|
+
import { getProcessWorkerNativeWaitU32, installProcessWorkerBootstrap, } from "./process-worker-bootstrap.js";
|
|
17
15
|
const WORKER_FATAL_MESSAGE_KEY = "__knittingWorkerFatal";
|
|
18
|
-
const getProcessWorkerNativeWaitU32 = () => {
|
|
19
|
-
if (!RUNTIME_IS_PROCESS_WORKER || RUNTIME !== "node")
|
|
20
|
-
return undefined;
|
|
21
|
-
try {
|
|
22
|
-
return loadNodeFutexAddon().waitU32;
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
16
|
const reportWorkerStartupFatal = (error) => {
|
|
29
17
|
const message = String(error?.message ?? error);
|
|
30
18
|
const payload = {
|
|
@@ -114,6 +102,11 @@ export const workerMainLoop = async (startupData) => {
|
|
|
114
102
|
const a_store = Atomics.store;
|
|
115
103
|
const a_load = Atomics.load;
|
|
116
104
|
const nativeWaitU32 = getProcessWorkerNativeWaitU32();
|
|
105
|
+
await runWorkerBootstrap({
|
|
106
|
+
bootstrap: workerOptions?.bootstrap,
|
|
107
|
+
thread,
|
|
108
|
+
totalNumberOfThread,
|
|
109
|
+
});
|
|
117
110
|
const listOfFunctions = await getFunctions({
|
|
118
111
|
list,
|
|
119
112
|
isWorker: true,
|
|
@@ -307,146 +300,18 @@ const installWorkerGlobalBootstrap = () => {
|
|
|
307
300
|
start(data);
|
|
308
301
|
};
|
|
309
302
|
};
|
|
310
|
-
const isProcessSharedBufferMetadata = (value) => {
|
|
311
|
-
try {
|
|
312
|
-
ProcessSharedBuffer.fromMetadata(value);
|
|
313
|
-
return true;
|
|
314
|
-
}
|
|
315
|
-
catch {
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
const isProcessWorkerWireLockBuffers = (value) => {
|
|
320
|
-
if (!value || typeof value !== "object")
|
|
321
|
-
return false;
|
|
322
|
-
const candidate = value;
|
|
323
|
-
return isProcessSharedBufferMetadata(candidate.headers) &&
|
|
324
|
-
isProcessSharedBufferMetadata(candidate.lockSector) &&
|
|
325
|
-
isProcessSharedBufferMetadata(candidate.payload) &&
|
|
326
|
-
isProcessSharedBufferMetadata(candidate.payloadSector) &&
|
|
327
|
-
(candidate.textCompat === undefined ||
|
|
328
|
-
isLockBufferTextCompat(candidate.textCompat));
|
|
329
|
-
};
|
|
330
|
-
const isProcessWorkerBootPayload = (value) => {
|
|
331
|
-
if (!value || typeof value !== "object")
|
|
332
|
-
return false;
|
|
333
|
-
const candidate = value;
|
|
334
|
-
const workerData = candidate.workerData;
|
|
335
|
-
return candidate.version === RUNTIME_PROCESS_WORKER_BOOT_VERSION &&
|
|
336
|
-
!!workerData &&
|
|
337
|
-
isProcessSharedBufferMetadata(workerData.sab) &&
|
|
338
|
-
Array.isArray(workerData.list) &&
|
|
339
|
-
Array.isArray(workerData.ids) &&
|
|
340
|
-
Array.isArray(workerData.at) &&
|
|
341
|
-
typeof workerData.thread === "number" &&
|
|
342
|
-
typeof workerData.totalNumberOfThread === "number" &&
|
|
343
|
-
typeof workerData.startAt === "number" &&
|
|
344
|
-
(workerData.abortSignalSAB === undefined ||
|
|
345
|
-
isProcessSharedBufferMetadata(workerData.abortSignalSAB)) &&
|
|
346
|
-
isProcessWorkerWireLockBuffers(workerData.lock) &&
|
|
347
|
-
isProcessWorkerWireLockBuffers(workerData.returnLock);
|
|
348
|
-
};
|
|
349
|
-
const getProcessWorkerPrimitives = () => {
|
|
350
|
-
const primitives = RUNTIME === "bun"
|
|
351
|
-
? createBunConnectionPrimitives()
|
|
352
|
-
: RUNTIME === "deno"
|
|
353
|
-
? createDenoConnectionPrimitives()
|
|
354
|
-
: getDefaultProcessSharedBufferPrimitives();
|
|
355
|
-
setDefaultProcessSharedBufferPrimitives(primitives);
|
|
356
|
-
return primitives;
|
|
357
|
-
};
|
|
358
|
-
const reviveProcessWorkerData = (wire) => {
|
|
359
|
-
const primitives = getProcessWorkerPrimitives();
|
|
360
|
-
const mappings = new Map();
|
|
361
|
-
const reviveRegion = (metadata) => {
|
|
362
|
-
const processBuffer = ProcessSharedBuffer.fromMetadata(metadata);
|
|
363
|
-
const descriptor = processBuffer.descriptor;
|
|
364
|
-
const key = `${descriptor.fd}:${descriptor.size}:${descriptor.runtime ?? ""}`;
|
|
365
|
-
let mapping = mappings.get(key);
|
|
366
|
-
if (mapping === undefined) {
|
|
367
|
-
mapping = descriptor.map(primitives);
|
|
368
|
-
mappings.set(key, mapping);
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
descriptor.attach(mapping);
|
|
372
|
-
}
|
|
373
|
-
return {
|
|
374
|
-
sab: mapping.buffer,
|
|
375
|
-
byteOffset: processBuffer.byteOffset,
|
|
376
|
-
byteLength: processBuffer.byteLength,
|
|
377
|
-
};
|
|
378
|
-
};
|
|
379
|
-
const reviveLockBuffers = (lock) => ({
|
|
380
|
-
...lock,
|
|
381
|
-
headers: reviveRegion(lock.headers),
|
|
382
|
-
lockSector: reviveRegion(lock.lockSector),
|
|
383
|
-
payload: reviveRegion(lock.payload),
|
|
384
|
-
payloadSector: reviveRegion(lock.payloadSector),
|
|
385
|
-
});
|
|
386
|
-
return {
|
|
387
|
-
...wire,
|
|
388
|
-
sab: reviveRegion(wire.sab),
|
|
389
|
-
abortSignalSAB: wire.abortSignalSAB === undefined
|
|
390
|
-
? undefined
|
|
391
|
-
: reviveRegion(wire.abortSignalSAB),
|
|
392
|
-
lock: reviveLockBuffers(wire.lock),
|
|
393
|
-
returnLock: reviveLockBuffers(wire.returnLock),
|
|
394
|
-
};
|
|
395
|
-
};
|
|
396
|
-
const installProcessWorkerBootstrap = () => {
|
|
397
|
-
const processLike = getNodeProcess();
|
|
398
|
-
const start = (payload) => {
|
|
399
|
-
const data = reviveProcessWorkerData(payload.workerData);
|
|
400
|
-
if (!isWorkerBootPayload(data)) {
|
|
401
|
-
throw new TypeError("invalid process worker boot payload");
|
|
402
|
-
}
|
|
403
|
-
void workerMainLoop(data).catch(reportWorkerStartupFatal);
|
|
404
|
-
};
|
|
405
|
-
const envBoot = processLike?.env?.[RUNTIME_PROCESS_WORKER_BOOT_ENV];
|
|
406
|
-
if (typeof envBoot === "string" && envBoot.length > 0) {
|
|
407
|
-
try {
|
|
408
|
-
const payload = JSON.parse(envBoot);
|
|
409
|
-
if (!isProcessWorkerBootPayload(payload)) {
|
|
410
|
-
throw new TypeError("invalid process worker boot payload");
|
|
411
|
-
}
|
|
412
|
-
try {
|
|
413
|
-
delete processLike?.env?.[RUNTIME_PROCESS_WORKER_BOOT_ENV];
|
|
414
|
-
}
|
|
415
|
-
catch {
|
|
416
|
-
}
|
|
417
|
-
start(payload);
|
|
418
|
-
}
|
|
419
|
-
catch (error) {
|
|
420
|
-
reportWorkerStartupFatal(error);
|
|
421
|
-
}
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
if (typeof processLike?.on !== "function")
|
|
425
|
-
return;
|
|
426
|
-
const onMessage = (message) => {
|
|
427
|
-
if (!isProcessWorkerBootPayload(message))
|
|
428
|
-
return;
|
|
429
|
-
try {
|
|
430
|
-
processLike.off?.("message", onMessage);
|
|
431
|
-
processLike.removeListener?.("message", onMessage);
|
|
432
|
-
}
|
|
433
|
-
catch {
|
|
434
|
-
}
|
|
435
|
-
try {
|
|
436
|
-
start(message);
|
|
437
|
-
}
|
|
438
|
-
catch (error) {
|
|
439
|
-
reportWorkerStartupFatal(error);
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
processLike.on("message", onMessage);
|
|
443
|
-
};
|
|
444
303
|
if (RUNTIME_IS_MAIN_THREAD === false &&
|
|
445
304
|
isWorkerBootPayload(RUNTIME_WORKER_DATA)) {
|
|
446
305
|
void workerMainLoop(RUNTIME_WORKER_DATA).catch(reportWorkerStartupFatal);
|
|
447
306
|
}
|
|
448
307
|
else if (RUNTIME_IS_PROCESS_WORKER) {
|
|
449
|
-
installProcessWorkerBootstrap(
|
|
308
|
+
installProcessWorkerBootstrap({
|
|
309
|
+
isWorkerBootPayload,
|
|
310
|
+
reportWorkerStartupFatal,
|
|
311
|
+
startWorker: (data) => {
|
|
312
|
+
void workerMainLoop(data).catch(reportWorkerStartupFatal);
|
|
313
|
+
},
|
|
314
|
+
});
|
|
450
315
|
}
|
|
451
316
|
else if (isWorkerGlobalScope()) {
|
|
452
317
|
installWorkerGlobalBootstrap();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { WorkerData } from "../types.js";
|
|
2
|
+
import type { NativeWaitU32 } from "./timers.js";
|
|
3
|
+
export declare const getProcessWorkerNativeWaitU32: () => NativeWaitU32 | undefined;
|
|
4
|
+
export declare const installProcessWorkerBootstrap: ({ isWorkerBootPayload, reportWorkerStartupFatal, startWorker, }: {
|
|
5
|
+
isWorkerBootPayload: (value: unknown) => value is WorkerData;
|
|
6
|
+
reportWorkerStartupFatal: (error: unknown) => void;
|
|
7
|
+
startWorker: (data: WorkerData) => void;
|
|
8
|
+
}) => void;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { RUNTIME } from "../common/runtime.js";
|
|
2
|
+
import { RUNTIME_IS_PROCESS_WORKER, RUNTIME_PARENT_PORT, RUNTIME_PROCESS_WORKER_BOOT_ENV, RUNTIME_PROCESS_WORKER_BOOT_VERSION, } from "../common/worker-runtime.js";
|
|
3
|
+
import { getNodeProcess } from "../common/node-compat.js";
|
|
4
|
+
import { isLockBufferTextCompat } from "../common/shared-buffer-text.js";
|
|
5
|
+
import { getDefaultProcessSharedBufferPrimitives, ProcessSharedBuffer, setDefaultProcessSharedBufferPrimitives, } from "../connections/process-shared-buffer.js";
|
|
6
|
+
import { createBunConnectionPrimitives } from "../connections/bun.js";
|
|
7
|
+
import { createDenoConnectionPrimitives } from "../connections/deno.js";
|
|
8
|
+
import { loadNodeFutexAddon } from "../connections/node.js";
|
|
9
|
+
export const getProcessWorkerNativeWaitU32 = () => {
|
|
10
|
+
if (!RUNTIME_IS_PROCESS_WORKER || RUNTIME !== "node")
|
|
11
|
+
return undefined;
|
|
12
|
+
try {
|
|
13
|
+
return loadNodeFutexAddon().waitU32;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const isProcessSharedBufferMetadata = (value) => {
|
|
20
|
+
try {
|
|
21
|
+
ProcessSharedBuffer.fromMetadata(value);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const isProcessWorkerWireLockBuffers = (value) => {
|
|
29
|
+
if (!value || typeof value !== "object")
|
|
30
|
+
return false;
|
|
31
|
+
const candidate = value;
|
|
32
|
+
return isProcessSharedBufferMetadata(candidate.headers) &&
|
|
33
|
+
isProcessSharedBufferMetadata(candidate.lockSector) &&
|
|
34
|
+
isProcessSharedBufferMetadata(candidate.payload) &&
|
|
35
|
+
isProcessSharedBufferMetadata(candidate.payloadSector) &&
|
|
36
|
+
(candidate.textCompat === undefined ||
|
|
37
|
+
isLockBufferTextCompat(candidate.textCompat));
|
|
38
|
+
};
|
|
39
|
+
const isProcessWorkerBootPayload = (value) => {
|
|
40
|
+
if (!value || typeof value !== "object")
|
|
41
|
+
return false;
|
|
42
|
+
const candidate = value;
|
|
43
|
+
const workerData = candidate.workerData;
|
|
44
|
+
return candidate.version === RUNTIME_PROCESS_WORKER_BOOT_VERSION &&
|
|
45
|
+
!!workerData &&
|
|
46
|
+
isProcessSharedBufferMetadata(workerData.sab) &&
|
|
47
|
+
Array.isArray(workerData.list) &&
|
|
48
|
+
Array.isArray(workerData.ids) &&
|
|
49
|
+
Array.isArray(workerData.at) &&
|
|
50
|
+
typeof workerData.thread === "number" &&
|
|
51
|
+
typeof workerData.totalNumberOfThread === "number" &&
|
|
52
|
+
typeof workerData.startAt === "number" &&
|
|
53
|
+
(workerData.abortSignalSAB === undefined ||
|
|
54
|
+
isProcessSharedBufferMetadata(workerData.abortSignalSAB)) &&
|
|
55
|
+
isProcessWorkerWireLockBuffers(workerData.lock) &&
|
|
56
|
+
isProcessWorkerWireLockBuffers(workerData.returnLock);
|
|
57
|
+
};
|
|
58
|
+
const getProcessWorkerPrimitives = () => {
|
|
59
|
+
const primitives = RUNTIME === "bun"
|
|
60
|
+
? createBunConnectionPrimitives()
|
|
61
|
+
: RUNTIME === "deno"
|
|
62
|
+
? createDenoConnectionPrimitives()
|
|
63
|
+
: getDefaultProcessSharedBufferPrimitives();
|
|
64
|
+
setDefaultProcessSharedBufferPrimitives(primitives);
|
|
65
|
+
return primitives;
|
|
66
|
+
};
|
|
67
|
+
const reviveProcessWorkerData = (wire) => {
|
|
68
|
+
const primitives = getProcessWorkerPrimitives();
|
|
69
|
+
const mappings = new Map();
|
|
70
|
+
const mappingKey = (descriptor) => descriptor.name === undefined
|
|
71
|
+
? `fd:${descriptor.fd}:${descriptor.size}:${descriptor.runtime ?? ""}`
|
|
72
|
+
: `name:${descriptor.name}:${descriptor.size}:${descriptor.runtime ?? ""}`;
|
|
73
|
+
const reviveRegion = (metadata) => {
|
|
74
|
+
const processBuffer = ProcessSharedBuffer.fromMetadata(metadata);
|
|
75
|
+
const descriptor = processBuffer.descriptor;
|
|
76
|
+
const key = mappingKey(descriptor);
|
|
77
|
+
let mapping = mappings.get(key);
|
|
78
|
+
if (mapping === undefined) {
|
|
79
|
+
mapping = descriptor.map(primitives);
|
|
80
|
+
mappings.set(key, mapping);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
descriptor.attach(mapping);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
sab: mapping.buffer,
|
|
87
|
+
byteOffset: processBuffer.byteOffset,
|
|
88
|
+
byteLength: processBuffer.byteLength,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
const reviveLockBuffers = (lock) => ({
|
|
92
|
+
...lock,
|
|
93
|
+
headers: reviveRegion(lock.headers),
|
|
94
|
+
lockSector: reviveRegion(lock.lockSector),
|
|
95
|
+
payload: reviveRegion(lock.payload),
|
|
96
|
+
payloadSector: reviveRegion(lock.payloadSector),
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
...wire,
|
|
100
|
+
sab: reviveRegion(wire.sab),
|
|
101
|
+
abortSignalSAB: wire.abortSignalSAB === undefined
|
|
102
|
+
? undefined
|
|
103
|
+
: reviveRegion(wire.abortSignalSAB),
|
|
104
|
+
lock: reviveLockBuffers(wire.lock),
|
|
105
|
+
returnLock: reviveLockBuffers(wire.returnLock),
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
export const installProcessWorkerBootstrap = ({ isWorkerBootPayload, reportWorkerStartupFatal, startWorker, }) => {
|
|
109
|
+
const processLike = getNodeProcess();
|
|
110
|
+
const start = (payload) => {
|
|
111
|
+
const data = reviveProcessWorkerData(payload.workerData);
|
|
112
|
+
if (!isWorkerBootPayload(data)) {
|
|
113
|
+
throw new TypeError("invalid process worker boot payload");
|
|
114
|
+
}
|
|
115
|
+
startWorker(data);
|
|
116
|
+
};
|
|
117
|
+
const envBoot = processLike?.env?.[RUNTIME_PROCESS_WORKER_BOOT_ENV];
|
|
118
|
+
if (typeof envBoot === "string" && envBoot.length > 0) {
|
|
119
|
+
try {
|
|
120
|
+
const payload = JSON.parse(envBoot);
|
|
121
|
+
if (!isProcessWorkerBootPayload(payload)) {
|
|
122
|
+
throw new TypeError("invalid process worker boot payload");
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
delete processLike?.env?.[RUNTIME_PROCESS_WORKER_BOOT_ENV];
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
}
|
|
129
|
+
start(payload);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
reportWorkerStartupFatal(error);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (RUNTIME_PARENT_PORT === undefined) {
|
|
137
|
+
reportWorkerStartupFatal(new TypeError("missing process worker boot payload"));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (typeof processLike?.on !== "function")
|
|
141
|
+
return;
|
|
142
|
+
const onMessage = (message) => {
|
|
143
|
+
if (!isProcessWorkerBootPayload(message))
|
|
144
|
+
return;
|
|
145
|
+
try {
|
|
146
|
+
processLike.off?.("message", onMessage);
|
|
147
|
+
processLike.removeListener?.("message", onMessage);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
start(message);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
reportWorkerStartupFatal(error);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
processLike.on("message", onMessage);
|
|
159
|
+
};
|
package/src/worker/timers.js
CHANGED
|
@@ -36,6 +36,21 @@ const waitFallbackView = a_wait === undefined
|
|
|
36
36
|
const a_pause = "pause" in Atomics
|
|
37
37
|
? Atomics.pause
|
|
38
38
|
: undefined;
|
|
39
|
+
const runtimeGlobals = globalThis;
|
|
40
|
+
const isPlainNodeWindows = runtimeGlobals.process?.platform === "win32" &&
|
|
41
|
+
typeof runtimeGlobals.process?.versions?.node === "string" &&
|
|
42
|
+
runtimeGlobals.process?.versions?.bun === undefined &&
|
|
43
|
+
runtimeGlobals.Deno === undefined;
|
|
44
|
+
// Windows has no working cross-process wake. The native wake (WakeByAddress)
|
|
45
|
+
// is keyed to a virtual address, so a host cannot wake a process worker parked
|
|
46
|
+
// on the same physical page mapped at a different address in the child — the
|
|
47
|
+
// addon's FutexWake is a no-op there. A parked Windows Node process worker can
|
|
48
|
+
// therefore never be signalled and must rediscover work by polling. Cap the
|
|
49
|
+
// native wait at 1ms so it re-checks every millisecond instead of sleeping the
|
|
50
|
+
// full parkMs (up to seconds); the long park with no wake is what made CI
|
|
51
|
+
// runners appear to hang. The native wait already polls internally, so this is
|
|
52
|
+
// just bounding each poll slice.
|
|
53
|
+
const nativeWaitTimeoutMs = (parkMs) => isPlainNodeWindows ? 1 : parkMs ?? 60;
|
|
39
54
|
export const whilePausing = ({ pauseInNanoseconds }) => {
|
|
40
55
|
const forNanoseconds = pauseInNanoseconds ?? DEFAULT_PAUSE_TIME;
|
|
41
56
|
if (!a_pause || forNanoseconds <= 0)
|
|
@@ -44,10 +59,9 @@ export const whilePausing = ({ pauseInNanoseconds }) => {
|
|
|
44
59
|
};
|
|
45
60
|
export const pauseGeneric = whilePausing({});
|
|
46
61
|
export const sleepUntilChanged = ({ at, opView, pauseInNanoseconds, rxStatus, txStatus, enqueueLock, write, nativeWaitU32, useSharedMemoryWait = true, }) => {
|
|
47
|
-
const pause = pauseInNanoseconds
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
: pauseGeneric;
|
|
62
|
+
const pause = pauseInNanoseconds === undefined
|
|
63
|
+
? pauseGeneric
|
|
64
|
+
: whilePausing({ pauseInNanoseconds });
|
|
51
65
|
const tryProgress = () => {
|
|
52
66
|
let progressed = false;
|
|
53
67
|
if (enqueueLock())
|
|
@@ -82,7 +96,7 @@ export const sleepUntilChanged = ({ at, opView, pauseInNanoseconds, rxStatus, tx
|
|
|
82
96
|
return;
|
|
83
97
|
a_store(rxStatus, 0, 0);
|
|
84
98
|
if (nativeWaitU32 !== undefined) {
|
|
85
|
-
nativeWaitU32(opView.buffer, opView.byteOffset + (at * Int32Array.BYTES_PER_ELEMENT), value >>> 0, parkMs
|
|
99
|
+
nativeWaitU32(opView.buffer, opView.byteOffset + (at * Int32Array.BYTES_PER_ELEMENT), value >>> 0, nativeWaitTimeoutMs(parkMs));
|
|
86
100
|
}
|
|
87
101
|
else if (useSharedMemoryWait &&
|
|
88
102
|
a_wait &&
|