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
@@ -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
- return Object.entries(module)
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();
@@ -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
- !== undefined
49
- ? whilePausing({ pauseInNanoseconds })
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 ?? 60);
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 &&