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.
Files changed (62) hide show
  1. package/README.md +326 -95
  2. package/map.md +52 -8
  3. package/package.json +4 -3
  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.d.ts +3 -2
  17. package/src/api.js +135 -34
  18. package/src/common/task-source.d.ts +1 -0
  19. package/src/common/task-source.js +5 -0
  20. package/src/connections/bun.d.ts +2 -0
  21. package/src/connections/bun.js +64 -9
  22. package/src/connections/deno.d.ts +2 -0
  23. package/src/connections/deno.js +64 -9
  24. package/src/connections/file-descriptor.d.ts +2 -0
  25. package/src/connections/file-descriptor.js +24 -2
  26. package/src/connections/node.d.ts +2 -1
  27. package/src/connections/node.js +17 -14
  28. package/src/connections/posix.d.ts +1 -0
  29. package/src/connections/posix.js +6 -0
  30. package/src/connections/process-shared-buffer.d.ts +3 -1
  31. package/src/connections/process-shared-buffer.js +19 -13
  32. package/src/connections/types.d.ts +2 -0
  33. package/src/connections/windows.d.ts +28 -0
  34. package/src/connections/windows.js +224 -0
  35. package/src/knitting_shared_memory.cc +319 -26
  36. package/src/knitting_windows_shared_memory.cc +156 -0
  37. package/src/memory/lock.js +8 -168
  38. package/src/memory/payloadCodec.js +28 -34
  39. package/src/memory/shared-buffer-io.d.ts +2 -0
  40. package/src/memory/shared-buffer-io.js +23 -0
  41. package/src/runtime/inline-executor.d.ts +2 -2
  42. package/src/runtime/inline-executor.js +15 -3
  43. package/src/runtime/pool.d.ts +3 -14
  44. package/src/runtime/pool.js +12 -543
  45. package/src/runtime/process-worker.d.ts +92 -0
  46. package/src/runtime/process-worker.js +670 -0
  47. package/src/runtime/tx-queue.d.ts +1 -1
  48. package/src/runtime/tx-queue.js +3 -1
  49. package/src/shared/abortSignal.d.ts +1 -1
  50. package/src/shared/abortSignal.js +10 -2
  51. package/src/types.d.ts +67 -8
  52. package/src/worker/bootstrap.d.ts +5 -0
  53. package/src/worker/bootstrap.js +78 -0
  54. package/src/worker/composable-runners.js +0 -8
  55. package/src/worker/loop.js +23 -156
  56. package/src/worker/process-worker-bootstrap.d.ts +8 -0
  57. package/src/worker/process-worker-bootstrap.js +160 -0
  58. package/src/worker/safety/startup.d.ts +2 -1
  59. package/src/worker/safety/startup.js +5 -2
  60. package/src/worker/task-loader.d.ts +2 -1
  61. package/src/worker/task-loader.js +14 -2
  62. 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 = deferred.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 AbortSignalOfTask<T extends TaskLike<any>> = T extends {
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]["f"], AbortSignalOfTask<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 "bun".
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 taskset.
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,5 @@
1
+ export declare const runWorkerBootstrap: ({ bootstrap, thread, totalNumberOfThread, }: {
2
+ bootstrap: unknown;
3
+ thread: number;
4
+ totalNumberOfThread: number;
5
+ }) => Promise<void>;
@@ -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));
@@ -1,30 +1,18 @@
1
- import { RUNTIME_IS_MAIN_THREAD, RUNTIME_IS_PROCESS_WORKER, RUNTIME_PARENT_PORT, RUNTIME_PROCESS_WORKER_BOOT_ENV, RUNTIME_PROCESS_WORKER_BOOT_VERSION, RUNTIME_WORKER_DATA, createRuntimeMessageChannel, } from "../common/worker-runtime.js";
2
- import { isSharedBufferSource, } from "../common/shared-buffer-region.js";
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, } from "../ipc/transport/shared-memory.js";
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, } from "./timers.js";
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 { getDefaultProcessSharedBufferPrimitives, ProcessSharedBuffer, setDefaultProcessSharedBufferPrimitives, } from "../connections/process-shared-buffer.js";
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;