knitting 0.1.46 → 0.1.51
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 +326 -95
- package/map.md +52 -8
- package/package.json +4 -3
- 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.d.ts +3 -2
- package/src/api.js +135 -34
- package/src/common/task-source.d.ts +1 -0
- package/src/common/task-source.js +5 -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 +19 -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 +319 -26
- package/src/knitting_windows_shared_memory.cc +156 -0
- package/src/memory/lock.js +8 -168
- package/src/memory/payloadCodec.js +28 -34
- package/src/memory/shared-buffer-io.d.ts +2 -0
- package/src/memory/shared-buffer-io.js +23 -0
- package/src/runtime/inline-executor.d.ts +2 -2
- package/src/runtime/inline-executor.js +15 -3
- package/src/runtime/pool.d.ts +3 -14
- package/src/runtime/pool.js +12 -543
- 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 +67 -8
- 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 +23 -156
- package/src/worker/process-worker-bootstrap.d.ts +8 -0
- package/src/worker/process-worker-bootstrap.js +160 -0
- package/src/worker/safety/startup.d.ts +2 -1
- package/src/worker/safety/startup.js +5 -2
- package/src/worker/task-loader.d.ts +2 -1
- package/src/worker/task-loader.js +14 -2
- 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
|
@@ -22,6 +22,7 @@ type WorkerData = {
|
|
|
22
22
|
abortSignalMax?: number;
|
|
23
23
|
list: string[];
|
|
24
24
|
ids: number[];
|
|
25
|
+
names: string[];
|
|
25
26
|
thread: number;
|
|
26
27
|
totalNumberOfThread: number;
|
|
27
28
|
debug?: DebugOptions;
|
|
@@ -81,8 +82,9 @@ type TaskLike<AS extends AbortSignalOption = AbortSignalOption> = {
|
|
|
81
82
|
} : {
|
|
82
83
|
readonly abortSignal: AS;
|
|
83
84
|
});
|
|
85
|
+
type TaskFunctionLike = (...args: any[]) => any;
|
|
84
86
|
type Composed<A extends TaskInput = Args, B extends Args = Args, AS extends AbortSignalOption = undefined> = FixPoint<A, B, AS> & SecondPart;
|
|
85
|
-
type tasks = Record<string, Composed<any, any, AbortSignalOption
|
|
87
|
+
type tasks = Record<string, Composed<any, any, AbortSignalOption> | TaskFunctionLike>;
|
|
86
88
|
type ComposedWithKey = Composed<any, any, AbortSignalOption> & {
|
|
87
89
|
name: string;
|
|
88
90
|
};
|
|
@@ -95,11 +97,12 @@ type NormalizeUndefinedSingleArg<T extends unknown[]> = T extends [undefined] ?
|
|
|
95
97
|
type AbortAwareCallArgs<T extends unknown[]> = T extends [...infer Head, AbortSignalToolkit<any>] ? NormalizeUndefinedSingleArg<Head> : NormalizeUndefinedSingleArg<T>;
|
|
96
98
|
type HostCallArgs<F extends (...args: any[]) => any, AS extends AbortSignalOption> = AS extends undefined ? Parameters<F> : AbortAwareCallArgs<Parameters<F>>;
|
|
97
99
|
type PromisifyCallArgs<F extends (...args: any[]) => any, AS extends AbortSignalOption> = HostCallArgs<F, AS> extends infer T ? T extends unknown[] ? PromisifyArgs<T> : never : never;
|
|
98
|
-
type
|
|
100
|
+
type TaskCallable<T> = T extends TaskLike<any> ? T["f"] : T extends TaskFunctionLike ? T : never;
|
|
101
|
+
type AbortSignalOfTask<T> = T extends {
|
|
99
102
|
readonly abortSignal: infer AS;
|
|
100
103
|
} ? Extract<AS, AbortSignalOption> : undefined;
|
|
101
|
-
type FunctionMapType<T extends Record<string, TaskLike<any
|
|
102
|
-
[K in keyof T]: PromiseWrapped<T[K]
|
|
104
|
+
type FunctionMapType<T extends Record<string, TaskLike<any> | TaskFunctionLike>> = {
|
|
105
|
+
[K in keyof T]: PromiseWrapped<TaskCallable<T[K]>, AbortSignalOfTask<T[K]>>;
|
|
103
106
|
};
|
|
104
107
|
interface FixPointBase<A extends TaskInput, B extends Args, AS extends AbortSignalOption = undefined> {
|
|
105
108
|
readonly f: TaskFn<A, B, AS>;
|
|
@@ -125,13 +128,23 @@ type SecondPart = {
|
|
|
125
128
|
*/
|
|
126
129
|
readonly at: number;
|
|
127
130
|
readonly importedFrom: string;
|
|
131
|
+
/**
|
|
132
|
+
* Marks a task whose worker-side function is imported dynamically (via
|
|
133
|
+
* `importTask`). Such tasks must never run on the host inliner lane: their
|
|
134
|
+
* module import is meant to happen inside the worker so worker permission
|
|
135
|
+
* policies apply. The pool routes them to worker lanes only, even when the
|
|
136
|
+
* inliner is enabled for other tasks.
|
|
137
|
+
*/
|
|
138
|
+
readonly imported?: boolean;
|
|
128
139
|
};
|
|
129
140
|
type SingleTaskPool<A extends TaskInput = Args, B extends Args = Args, AS extends AbortSignalOption = undefined> = {
|
|
130
141
|
call: PromiseWrapped<TaskFn<A, B, AS>, AS>;
|
|
131
142
|
shutdown: (delayMs?: number) => Promise<void>;
|
|
143
|
+
[Symbol.dispose]: () => void;
|
|
132
144
|
};
|
|
133
|
-
type Pool<T extends Record<string, TaskLike<any
|
|
145
|
+
type Pool<T extends Record<string, TaskLike<any> | TaskFunctionLike>> = {
|
|
134
146
|
shutdown: (delayMs?: number) => Promise<void>;
|
|
147
|
+
[Symbol.dispose]: () => void;
|
|
135
148
|
call: FunctionMapType<T>;
|
|
136
149
|
};
|
|
137
150
|
type ReturnFixed<A extends TaskInput = undefined, B extends Args = undefined, AS extends AbortSignalOption = undefined> = FixPoint<A, B, AS> & SecondPart & {
|
|
@@ -164,8 +177,34 @@ type DebugOptions = {
|
|
|
164
177
|
logHref?: boolean;
|
|
165
178
|
logImportedUrl?: boolean;
|
|
166
179
|
};
|
|
180
|
+
type WorkerBootstrapContext = {
|
|
181
|
+
readonly thread: number;
|
|
182
|
+
readonly totalNumberOfThread: number;
|
|
183
|
+
readonly runtime: "node" | "deno" | "bun" | "unknown";
|
|
184
|
+
};
|
|
185
|
+
type WorkerBootstrapFunction<Data = unknown> = (data: Data, context: WorkerBootstrapContext) => MaybePromise<void>;
|
|
186
|
+
type WorkerBootstrapOptions<Data = unknown> = {
|
|
187
|
+
/**
|
|
188
|
+
* Module imported inside the worker before task modules are imported.
|
|
189
|
+
* Relative paths are resolved from the `createPool(...)` caller.
|
|
190
|
+
*/
|
|
191
|
+
href: string;
|
|
192
|
+
/**
|
|
193
|
+
* Exported bootstrap function name. Defaults to `"default"`.
|
|
194
|
+
*/
|
|
195
|
+
name?: string;
|
|
196
|
+
/**
|
|
197
|
+
* Structured data passed to the bootstrap function.
|
|
198
|
+
*/
|
|
199
|
+
data?: Data;
|
|
200
|
+
};
|
|
167
201
|
type WorkerSettings = {
|
|
168
202
|
resolveAfterFinishingAll?: true;
|
|
203
|
+
/**
|
|
204
|
+
* Privileged async worker hook that runs once before task modules import.
|
|
205
|
+
* Use it to shape the worker environment before user task code loads.
|
|
206
|
+
*/
|
|
207
|
+
bootstrap?: WorkerBootstrapOptions;
|
|
169
208
|
/**
|
|
170
209
|
* Experimental worker runtime.
|
|
171
210
|
* "thread" uses Worker/worker_threads. "process" spawns another JavaScript
|
|
@@ -173,17 +212,31 @@ type WorkerSettings = {
|
|
|
173
212
|
*/
|
|
174
213
|
runtime?: "thread" | "process";
|
|
175
214
|
/**
|
|
176
|
-
* Runtime executable to use when runtime is "process". Defaults to "
|
|
215
|
+
* Runtime executable to use when runtime is "process". Defaults to "deno".
|
|
177
216
|
*/
|
|
178
217
|
processRuntime?: "bun" | "deno" | "node";
|
|
179
218
|
/**
|
|
180
219
|
* Command argv to prepend before the process worker runtime command.
|
|
181
|
-
* Useful for wrappers such as systemd-run, cgexec, nice, or
|
|
220
|
+
* Useful for wrappers such as systemd-run, cgexec, nice, taskset, or
|
|
221
|
+
* docker. Knitting appends the runtime command after this prefix.
|
|
182
222
|
*
|
|
183
223
|
* Example:
|
|
184
224
|
* ["systemd-run", "--scope", "-p", "MemoryMax=500M", "-p", "CPUQuota=25%"]
|
|
225
|
+
*
|
|
226
|
+
* With containers, use processSharedMemory: "named", share the IPC
|
|
227
|
+
* namespace, mount the worker files at the same path, and forward
|
|
228
|
+
* KNITTING_PROCESS_WORKER plus KNITTING_PROCESS_WORKER_BOOT.
|
|
185
229
|
*/
|
|
186
230
|
processCommandPrefix?: string[];
|
|
231
|
+
/**
|
|
232
|
+
* How process workers discover their shared-memory control channel.
|
|
233
|
+
*
|
|
234
|
+
* "inherit" keeps the POSIX fd-inheritance path and is the default outside
|
|
235
|
+
* Windows. "named" creates an OS-named shared-memory object that wrappers
|
|
236
|
+
* such as containers can reopen by name when they share the same IPC
|
|
237
|
+
* namespace.
|
|
238
|
+
*/
|
|
239
|
+
processSharedMemory?: ProcessSharedMemoryMode | ProcessSharedMemorySettings;
|
|
187
240
|
timers?: WorkerTimers;
|
|
188
241
|
/**
|
|
189
242
|
* Hard task execution timeout in milliseconds.
|
|
@@ -191,6 +244,12 @@ type WorkerSettings = {
|
|
|
191
244
|
*/
|
|
192
245
|
hardTimeoutMs?: number;
|
|
193
246
|
};
|
|
247
|
+
type ProcessSharedMemoryMode = "inherit" | "named";
|
|
248
|
+
type ProcessSharedMemorySettings = {
|
|
249
|
+
mode?: ProcessSharedMemoryMode;
|
|
250
|
+
namePrefix?: string;
|
|
251
|
+
unlinkOnShutdown?: boolean;
|
|
252
|
+
};
|
|
194
253
|
type WorkerTimers = {
|
|
195
254
|
/**
|
|
196
255
|
* Busy-spin budget before parking (microseconds).
|
|
@@ -277,7 +336,7 @@ type CreatePool = {
|
|
|
277
336
|
debug?: DebugOptions;
|
|
278
337
|
source?: string;
|
|
279
338
|
};
|
|
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, };
|
|
339
|
+
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
340
|
export type { Task as Task } from "./memory/lock.js";
|
|
282
341
|
export { LockBound as LockBound, PayloadBuffer as PayloadBuffer, PayloadSignal as PayloadSignal, TaskIndex as TaskIndex, } from "./memory/lock.js";
|
|
283
342
|
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 = {
|
|
@@ -65,7 +53,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
65
53
|
installTerminationGuard();
|
|
66
54
|
installUnhandledRejectionSilencer();
|
|
67
55
|
installPerformanceNowGuard();
|
|
68
|
-
const { debug, sab, thread, startAt, workerOptions, lock, returnLock, abortSignalSAB, abortSignalMax, payloadConfig, permission, totalNumberOfThread, list, ids, at, } = startupData;
|
|
56
|
+
const { debug, sab, thread, startAt, workerOptions, lock, returnLock, abortSignalSAB, abortSignalMax, payloadConfig, permission, totalNumberOfThread, list, ids, names, at, } = startupData;
|
|
69
57
|
scrubWorkerDataSensitiveBuffers(startupData);
|
|
70
58
|
assertWorkerSharedMemoryBootData({ sab, lock, returnLock });
|
|
71
59
|
let Comment;
|
|
@@ -114,14 +102,20 @@ 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,
|
|
120
113
|
ids,
|
|
114
|
+
names,
|
|
121
115
|
at,
|
|
122
116
|
permission,
|
|
123
117
|
});
|
|
124
|
-
assertWorkerImportsResolved({ debug, list, ids, listOfFunctions });
|
|
118
|
+
assertWorkerImportsResolved({ debug, list, ids, names, listOfFunctions });
|
|
125
119
|
const abortSignals = abortSignalSAB
|
|
126
120
|
? signalAbortFactory({
|
|
127
121
|
sab: abortSignalSAB,
|
|
@@ -267,6 +261,7 @@ const isWorkerBootPayload = (value) => {
|
|
|
267
261
|
return isSharedBufferSource(candidate.sab) &&
|
|
268
262
|
Array.isArray(candidate.list) &&
|
|
269
263
|
Array.isArray(candidate.ids) &&
|
|
264
|
+
Array.isArray(candidate.names) &&
|
|
270
265
|
Array.isArray(candidate.at) &&
|
|
271
266
|
typeof candidate.thread === "number" &&
|
|
272
267
|
typeof candidate.totalNumberOfThread === "number" &&
|
|
@@ -307,146 +302,18 @@ const installWorkerGlobalBootstrap = () => {
|
|
|
307
302
|
start(data);
|
|
308
303
|
};
|
|
309
304
|
};
|
|
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
305
|
if (RUNTIME_IS_MAIN_THREAD === false &&
|
|
445
306
|
isWorkerBootPayload(RUNTIME_WORKER_DATA)) {
|
|
446
307
|
void workerMainLoop(RUNTIME_WORKER_DATA).catch(reportWorkerStartupFatal);
|
|
447
308
|
}
|
|
448
309
|
else if (RUNTIME_IS_PROCESS_WORKER) {
|
|
449
|
-
installProcessWorkerBootstrap(
|
|
310
|
+
installProcessWorkerBootstrap({
|
|
311
|
+
isWorkerBootPayload,
|
|
312
|
+
reportWorkerStartupFatal,
|
|
313
|
+
startWorker: (data) => {
|
|
314
|
+
void workerMainLoop(data).catch(reportWorkerStartupFatal);
|
|
315
|
+
},
|
|
316
|
+
});
|
|
450
317
|
}
|
|
451
318
|
else if (isWorkerGlobalScope()) {
|
|
452
319
|
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;
|