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
|
@@ -0,0 +1,160 @@
|
|
|
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.names) &&
|
|
50
|
+
Array.isArray(workerData.at) &&
|
|
51
|
+
typeof workerData.thread === "number" &&
|
|
52
|
+
typeof workerData.totalNumberOfThread === "number" &&
|
|
53
|
+
typeof workerData.startAt === "number" &&
|
|
54
|
+
(workerData.abortSignalSAB === undefined ||
|
|
55
|
+
isProcessSharedBufferMetadata(workerData.abortSignalSAB)) &&
|
|
56
|
+
isProcessWorkerWireLockBuffers(workerData.lock) &&
|
|
57
|
+
isProcessWorkerWireLockBuffers(workerData.returnLock);
|
|
58
|
+
};
|
|
59
|
+
const getProcessWorkerPrimitives = () => {
|
|
60
|
+
const primitives = RUNTIME === "bun"
|
|
61
|
+
? createBunConnectionPrimitives()
|
|
62
|
+
: RUNTIME === "deno"
|
|
63
|
+
? createDenoConnectionPrimitives()
|
|
64
|
+
: getDefaultProcessSharedBufferPrimitives();
|
|
65
|
+
setDefaultProcessSharedBufferPrimitives(primitives);
|
|
66
|
+
return primitives;
|
|
67
|
+
};
|
|
68
|
+
const reviveProcessWorkerData = (wire) => {
|
|
69
|
+
const primitives = getProcessWorkerPrimitives();
|
|
70
|
+
const mappings = new Map();
|
|
71
|
+
const mappingKey = (descriptor) => descriptor.name === undefined
|
|
72
|
+
? `fd:${descriptor.fd}:${descriptor.size}:${descriptor.runtime ?? ""}`
|
|
73
|
+
: `name:${descriptor.name}:${descriptor.size}:${descriptor.runtime ?? ""}`;
|
|
74
|
+
const reviveRegion = (metadata) => {
|
|
75
|
+
const processBuffer = ProcessSharedBuffer.fromMetadata(metadata);
|
|
76
|
+
const descriptor = processBuffer.descriptor;
|
|
77
|
+
const key = mappingKey(descriptor);
|
|
78
|
+
let mapping = mappings.get(key);
|
|
79
|
+
if (mapping === undefined) {
|
|
80
|
+
mapping = descriptor.map(primitives);
|
|
81
|
+
mappings.set(key, mapping);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
descriptor.attach(mapping);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
sab: mapping.buffer,
|
|
88
|
+
byteOffset: processBuffer.byteOffset,
|
|
89
|
+
byteLength: processBuffer.byteLength,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
const reviveLockBuffers = (lock) => ({
|
|
93
|
+
...lock,
|
|
94
|
+
headers: reviveRegion(lock.headers),
|
|
95
|
+
lockSector: reviveRegion(lock.lockSector),
|
|
96
|
+
payload: reviveRegion(lock.payload),
|
|
97
|
+
payloadSector: reviveRegion(lock.payloadSector),
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
...wire,
|
|
101
|
+
sab: reviveRegion(wire.sab),
|
|
102
|
+
abortSignalSAB: wire.abortSignalSAB === undefined
|
|
103
|
+
? undefined
|
|
104
|
+
: reviveRegion(wire.abortSignalSAB),
|
|
105
|
+
lock: reviveLockBuffers(wire.lock),
|
|
106
|
+
returnLock: reviveLockBuffers(wire.returnLock),
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
export const installProcessWorkerBootstrap = ({ isWorkerBootPayload, reportWorkerStartupFatal, startWorker, }) => {
|
|
110
|
+
const processLike = getNodeProcess();
|
|
111
|
+
const start = (payload) => {
|
|
112
|
+
const data = reviveProcessWorkerData(payload.workerData);
|
|
113
|
+
if (!isWorkerBootPayload(data)) {
|
|
114
|
+
throw new TypeError("invalid process worker boot payload");
|
|
115
|
+
}
|
|
116
|
+
startWorker(data);
|
|
117
|
+
};
|
|
118
|
+
const envBoot = processLike?.env?.[RUNTIME_PROCESS_WORKER_BOOT_ENV];
|
|
119
|
+
if (typeof envBoot === "string" && envBoot.length > 0) {
|
|
120
|
+
try {
|
|
121
|
+
const payload = JSON.parse(envBoot);
|
|
122
|
+
if (!isProcessWorkerBootPayload(payload)) {
|
|
123
|
+
throw new TypeError("invalid process worker boot payload");
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
delete processLike?.env?.[RUNTIME_PROCESS_WORKER_BOOT_ENV];
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
}
|
|
130
|
+
start(payload);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
reportWorkerStartupFatal(error);
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (RUNTIME_PARENT_PORT === undefined) {
|
|
138
|
+
reportWorkerStartupFatal(new TypeError("missing process worker boot payload"));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (typeof processLike?.on !== "function")
|
|
142
|
+
return;
|
|
143
|
+
const onMessage = (message) => {
|
|
144
|
+
if (!isProcessWorkerBootPayload(message))
|
|
145
|
+
return;
|
|
146
|
+
try {
|
|
147
|
+
processLike.off?.("message", onMessage);
|
|
148
|
+
processLike.removeListener?.("message", onMessage);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
start(message);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
reportWorkerStartupFatal(error);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
processLike.on("message", onMessage);
|
|
160
|
+
};
|
|
@@ -9,8 +9,9 @@ type ImportedFunctionsState = {
|
|
|
9
9
|
debug: DebugOptions | undefined;
|
|
10
10
|
list: string[];
|
|
11
11
|
ids: number[];
|
|
12
|
+
names?: string[];
|
|
12
13
|
listOfFunctions: readonly unknown[];
|
|
13
14
|
};
|
|
14
15
|
export declare const assertWorkerSharedMemoryBootData: ({ sab, lock, returnLock }: SharedMemoryBootData) => void;
|
|
15
|
-
export declare const assertWorkerImportsResolved: ({ debug, list, ids, listOfFunctions }: ImportedFunctionsState) => void;
|
|
16
|
+
export declare const assertWorkerImportsResolved: ({ debug, list, ids, names, listOfFunctions }: ImportedFunctionsState) => void;
|
|
16
17
|
export {};
|
|
@@ -17,14 +17,17 @@ export const assertWorkerSharedMemoryBootData = ({ sab, lock, returnLock }) => {
|
|
|
17
17
|
throw new Error("worker missing return lock SABs");
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
-
export const assertWorkerImportsResolved = ({ debug, list, ids, listOfFunctions }) => {
|
|
20
|
+
export const assertWorkerImportsResolved = ({ debug, list, ids, names, listOfFunctions }) => {
|
|
21
21
|
if (debug?.logImportedUrl === true) {
|
|
22
22
|
console.log(list);
|
|
23
23
|
}
|
|
24
|
-
if (listOfFunctions.length > 0
|
|
24
|
+
if (listOfFunctions.length > 0 &&
|
|
25
|
+
(names === undefined || listOfFunctions.length === names.length))
|
|
25
26
|
return;
|
|
26
27
|
console.log(list);
|
|
27
28
|
console.log(ids);
|
|
29
|
+
if (names !== undefined)
|
|
30
|
+
console.log(names);
|
|
28
31
|
console.log(listOfFunctions);
|
|
29
32
|
throw new Error("No imports were found.");
|
|
30
33
|
};
|
|
@@ -3,6 +3,7 @@ import type { ResolvedPermissionProtocol } from "../permission/protocol.js";
|
|
|
3
3
|
type GetFunctionParams = {
|
|
4
4
|
list: string[];
|
|
5
5
|
ids: number[];
|
|
6
|
+
names: string[];
|
|
6
7
|
at: number[];
|
|
7
8
|
isWorker: boolean;
|
|
8
9
|
permission?: ResolvedPermissionProtocol;
|
|
@@ -21,6 +22,6 @@ export type WorkerComposedWithKey = ComposedWithKey & {
|
|
|
21
22
|
run: WorkerCallable;
|
|
22
23
|
timeout?: TimeoutSpec;
|
|
23
24
|
};
|
|
24
|
-
export declare const getFunctions: ({ list, ids, at, permission }: GetFunctionParams) => Promise<WorkerComposedWithKey[]>;
|
|
25
|
+
export declare const getFunctions: ({ list, ids, names, at, permission }: GetFunctionParams) => Promise<WorkerComposedWithKey[]>;
|
|
25
26
|
export type GetFunctions = ReturnType<typeof getFunctions>;
|
|
26
27
|
export {};
|
|
@@ -34,11 +34,12 @@ const normalizeTimeout = (timeout) => {
|
|
|
34
34
|
const composeWorkerCallable = (fixed, _permission) => {
|
|
35
35
|
return fixed.f;
|
|
36
36
|
};
|
|
37
|
-
export const getFunctions = async ({ list, ids, at, permission }) => {
|
|
37
|
+
export const getFunctions = async ({ list, ids, names, at, permission }) => {
|
|
38
38
|
const modules = list.map((specifier) => toModuleUrl(specifier));
|
|
39
|
+
const nameSet = new Set(names);
|
|
39
40
|
const results = await Promise.all(modules.map(async (imports) => {
|
|
40
41
|
const module = (await import(__rewriteRelativeImportExtension(imports)));
|
|
41
|
-
|
|
42
|
+
const fixedTasks = Object.entries(module)
|
|
42
43
|
.filter(([_, value]) => value != null && typeof value === "object" &&
|
|
43
44
|
//@ts-ignore Reason -> trust me
|
|
44
45
|
value?.[endpointSymbol] === true)
|
|
@@ -47,6 +48,17 @@ export const getFunctions = async ({ list, ids, at, permission }) => {
|
|
|
47
48
|
...value,
|
|
48
49
|
name,
|
|
49
50
|
}));
|
|
51
|
+
const functionTasks = Object.entries(module)
|
|
52
|
+
.filter(([name, value]) => nameSet.has(name) && typeof value === "function")
|
|
53
|
+
.map(([name, value]) => ({
|
|
54
|
+
f: value,
|
|
55
|
+
id: -1,
|
|
56
|
+
importedFrom: imports,
|
|
57
|
+
at: -1,
|
|
58
|
+
name,
|
|
59
|
+
[endpointSymbol]: true,
|
|
60
|
+
}));
|
|
61
|
+
return [...fixedTasks, ...functionTasks];
|
|
50
62
|
}));
|
|
51
63
|
// Flatten the results, filter by IDs, and sort
|
|
52
64
|
const flattened = results.flat();
|
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 &&
|