knitting 0.1.52 → 0.1.54
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 +36 -25
- package/package.json +1 -1
- package/src/api.d.ts +26 -6
- package/src/api.js +147 -9
- package/src/connections/buffer-reference.d.ts +4 -2
- package/src/connections/buffer-reference.js +4 -2
- package/src/connections/process-shared-buffer.d.ts +6 -0
- package/src/connections/process-shared-buffer.js +6 -0
- package/src/debug/env-diff.d.ts +26 -0
- package/src/debug/env-diff.js +49 -0
- package/src/debug/gate.d.ts +18 -0
- package/src/debug/gate.js +69 -0
- package/src/debug/handle.d.ts +23 -0
- package/src/debug/handle.js +48 -0
- package/src/ipc/transport/shared-memory.d.ts +1 -3
- package/src/memory/payload-config.d.ts +9 -0
- package/src/permission/protocol.d.ts +1 -1
- package/src/permission/protocol.js +30 -20
- package/src/runtime/dispatcher.d.ts +6 -0
- package/src/runtime/pool.d.ts +11 -5
- package/src/runtime/pool.js +50 -44
- package/src/runtime/process-worker.js +2 -1
- package/src/runtime/tx-queue.js +5 -1
- package/src/types.d.ts +45 -38
- package/src/utils/http.d.ts +8 -0
- package/src/utils/http.js +7 -0
- package/src/worker/loop.js +53 -11
- package/src/worker/safety/startup.d.ts +2 -3
- package/src/worker/safety/startup.js +1 -4
- package/src/worker/timers.js +8 -3
package/src/utils/http.d.ts
CHANGED
|
@@ -3,13 +3,21 @@ export type NumberFormat = "f64" | "f32" | "i32";
|
|
|
3
3
|
export type NumberBufferOptions = {
|
|
4
4
|
format?: NumberFormat;
|
|
5
5
|
};
|
|
6
|
+
/** View ArrayBuffer/SharedArrayBuffer/typed-array bytes as Uint8Array. */
|
|
6
7
|
export declare const bufferToBytes: (source: BufferLike) => Uint8Array;
|
|
8
|
+
/** Copy bytes into a SharedArrayBuffer for zero-copy worker inputs. */
|
|
7
9
|
export declare const bytesToBuffer: (source: BufferLike) => SharedArrayBuffer;
|
|
10
|
+
/** Decode UTF-8 bytes from ArrayBuffer/SharedArrayBuffer/typed-array input. */
|
|
8
11
|
export declare const bufferToString: (source: BufferLike) => string;
|
|
12
|
+
/** Encode UTF-8 text into a SharedArrayBuffer. */
|
|
9
13
|
export declare const stringToBuffer: (text: string) => SharedArrayBuffer;
|
|
14
|
+
/** Decode JSON from UTF-8 bytes. */
|
|
10
15
|
export declare const bufferToJson: <T = unknown>(source: BufferLike) => T;
|
|
16
|
+
/** Encode JSON into a SharedArrayBuffer. */
|
|
11
17
|
export declare const jsonToBuffer: (value: unknown) => SharedArrayBuffer;
|
|
18
|
+
/** Encode numbers into a SharedArrayBuffer (`f64` by default). */
|
|
12
19
|
export declare const numbersToBuffer: (numbers: ArrayLike<number>, options?: NumberBufferOptions) => SharedArrayBuffer;
|
|
20
|
+
/** Decode numeric typed-array views from worker-friendly byte buffers. */
|
|
13
21
|
export declare function bufferToNumbers(source: BufferLike, options?: {
|
|
14
22
|
format?: "f64";
|
|
15
23
|
}): Float64Array;
|
package/src/utils/http.js
CHANGED
|
@@ -21,7 +21,9 @@ const toBytes = (source) => {
|
|
|
21
21
|
}
|
|
22
22
|
throw new TypeError("Expected an ArrayBuffer, SharedArrayBuffer, or typed-array view.");
|
|
23
23
|
};
|
|
24
|
+
/** View ArrayBuffer/SharedArrayBuffer/typed-array bytes as Uint8Array. */
|
|
24
25
|
export const bufferToBytes = (source) => toBytes(source);
|
|
26
|
+
/** Copy bytes into a SharedArrayBuffer for zero-copy worker inputs. */
|
|
25
27
|
export const bytesToBuffer = (source) => {
|
|
26
28
|
const bytes = toBytes(source);
|
|
27
29
|
const sab = new SharedArrayBuffer(bytes.byteLength);
|
|
@@ -38,6 +40,7 @@ const makeNumberView = (format, buffer, byteOffset, length) => {
|
|
|
38
40
|
return new Int32Array(buffer, byteOffset, length);
|
|
39
41
|
}
|
|
40
42
|
};
|
|
43
|
+
/** Decode UTF-8 bytes from ArrayBuffer/SharedArrayBuffer/typed-array input. */
|
|
41
44
|
export const bufferToString = (source) => {
|
|
42
45
|
const bytes = toBytes(source);
|
|
43
46
|
if (hasNodeBuffer) {
|
|
@@ -47,6 +50,7 @@ export const bufferToString = (source) => {
|
|
|
47
50
|
}
|
|
48
51
|
return textDecoder.decode(bytes);
|
|
49
52
|
};
|
|
53
|
+
/** Encode UTF-8 text into a SharedArrayBuffer. */
|
|
50
54
|
export const stringToBuffer = (text) => {
|
|
51
55
|
if (typeof text !== "string") {
|
|
52
56
|
throw new TypeError("stringToBuffer expects a string.");
|
|
@@ -62,7 +66,9 @@ export const stringToBuffer = (text) => {
|
|
|
62
66
|
new Uint8Array(sab).set(encoded);
|
|
63
67
|
return sab;
|
|
64
68
|
};
|
|
69
|
+
/** Decode JSON from UTF-8 bytes. */
|
|
65
70
|
export const bufferToJson = (source) => JSON.parse(bufferToString(source));
|
|
71
|
+
/** Encode JSON into a SharedArrayBuffer. */
|
|
66
72
|
export const jsonToBuffer = (value) => {
|
|
67
73
|
const text = JSON.stringify(value);
|
|
68
74
|
if (typeof text !== "string") {
|
|
@@ -70,6 +76,7 @@ export const jsonToBuffer = (value) => {
|
|
|
70
76
|
}
|
|
71
77
|
return stringToBuffer(text);
|
|
72
78
|
};
|
|
79
|
+
/** Encode numbers into a SharedArrayBuffer (`f64` by default). */
|
|
73
80
|
export const numbersToBuffer = (numbers, options) => {
|
|
74
81
|
const format = options?.format ?? "f64";
|
|
75
82
|
const sab = new SharedArrayBuffer(numbers.length * BYTES_PER_ELEMENT[format]);
|
package/src/worker/loop.js
CHANGED
|
@@ -13,6 +13,7 @@ import { signalAbortFactory } from "../shared/abortSignal.js";
|
|
|
13
13
|
import { runWorkerBootstrap } from "./bootstrap.js";
|
|
14
14
|
import { getProcessWorkerNativeWaitU32, installProcessWorkerBootstrap, } from "./process-worker-bootstrap.js";
|
|
15
15
|
import { readBufferReferenceReturnReleaseMessage, } from "../connections/buffer-reference.js";
|
|
16
|
+
import { resolveDebugNamespaces } from "../debug/gate.js";
|
|
16
17
|
const WORKER_FATAL_MESSAGE_KEY = "__knittingWorkerFatal";
|
|
17
18
|
const reportWorkerStartupFatal = (error) => {
|
|
18
19
|
const message = String(error?.message ?? error);
|
|
@@ -72,6 +73,14 @@ export const workerMainLoop = async (startupData) => {
|
|
|
72
73
|
const { debug, sab, thread, startAt, workerOptions, lock, returnLock, abortSignalSAB, abortSignalMax, payloadConfig, bufferReferenceReturn, permission, totalNumberOfThread, list, ids, names, at, } = startupData;
|
|
73
74
|
scrubWorkerDataSensitiveBuffers(startupData);
|
|
74
75
|
assertWorkerSharedMemoryBootData({ sab, lock, returnLock });
|
|
76
|
+
const debugNamespaces = resolveDebugNamespaces(debug);
|
|
77
|
+
const dbg = debugNamespaces.size > 0
|
|
78
|
+
? await import("../debug/handle.js").then((module) => module.initDebug({
|
|
79
|
+
name: `w${thread}`,
|
|
80
|
+
runtime: RUNTIME,
|
|
81
|
+
namespaces: debugNamespaces,
|
|
82
|
+
}))
|
|
83
|
+
: undefined;
|
|
75
84
|
let Comment;
|
|
76
85
|
(function (Comment) {
|
|
77
86
|
Comment[Comment["thisIsAHint"] = 0] = "thisIsAHint";
|
|
@@ -82,7 +91,6 @@ export const workerMainLoop = async (startupData) => {
|
|
|
82
91
|
},
|
|
83
92
|
isMain: false,
|
|
84
93
|
thread,
|
|
85
|
-
debug,
|
|
86
94
|
startTime: startAt,
|
|
87
95
|
});
|
|
88
96
|
const lockState = lock2({
|
|
@@ -106,8 +114,10 @@ export const workerMainLoop = async (startupData) => {
|
|
|
106
114
|
const timers = workerOptions?.timers;
|
|
107
115
|
const spinMicroseconds = timers?.spinMicroseconds ??
|
|
108
116
|
Math.max(1, totalNumberOfThread) * 50;
|
|
109
|
-
const parkMs =
|
|
110
|
-
|
|
117
|
+
const parkMs = dbg !== undefined
|
|
118
|
+
? Number.POSITIVE_INFINITY
|
|
119
|
+
: (timers?.parkMs ??
|
|
120
|
+
Math.max(1, totalNumberOfThread) * 50);
|
|
111
121
|
const pauseSpin = (() => {
|
|
112
122
|
const fn = typeof timers?.pauseNanoseconds === "number"
|
|
113
123
|
? whilePausing({ pauseInNanoseconds: timers.pauseNanoseconds })
|
|
@@ -123,6 +133,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
123
133
|
thread,
|
|
124
134
|
totalNumberOfThread,
|
|
125
135
|
});
|
|
136
|
+
dbg?.envPhase("bootstrap");
|
|
126
137
|
const listOfFunctions = await getFunctions({
|
|
127
138
|
list,
|
|
128
139
|
isWorker: true,
|
|
@@ -131,7 +142,9 @@ export const workerMainLoop = async (startupData) => {
|
|
|
131
142
|
at,
|
|
132
143
|
permission,
|
|
133
144
|
});
|
|
134
|
-
|
|
145
|
+
dbg?.envPhase("tasks");
|
|
146
|
+
dbg?.log("imports", `${listOfFunctions.length} task(s) from ${list.map((spec) => spec.split(/[\\/]/).pop() || spec).join(", ")}`);
|
|
147
|
+
assertWorkerImportsResolved({ list, ids, names, listOfFunctions });
|
|
135
148
|
const abortSignals = abortSignalSAB
|
|
136
149
|
? signalAbortFactory({
|
|
137
150
|
sab: abortSignalSAB,
|
|
@@ -170,7 +183,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
170
183
|
let awaitingSpins = 0;
|
|
171
184
|
let lastAwaiting = 0;
|
|
172
185
|
const MAX_AWAITING_MS = 10;
|
|
173
|
-
let
|
|
186
|
+
let wakeToken = a_load(opView, 0);
|
|
174
187
|
const scheduleMacro = () => {
|
|
175
188
|
if (isInMacro)
|
|
176
189
|
return;
|
|
@@ -195,15 +208,43 @@ export const workerMainLoop = async (startupData) => {
|
|
|
195
208
|
}
|
|
196
209
|
post2(null);
|
|
197
210
|
};
|
|
198
|
-
const
|
|
211
|
+
const traceSignals = dbg?.enabled("signals") === true;
|
|
199
212
|
const _hasCompleted = hasCompleted;
|
|
200
|
-
const _writeBatch = writeBatch;
|
|
201
213
|
const _hasPending = hasPending;
|
|
202
|
-
const _serviceBatchImmediate = serviceBatchImmediate;
|
|
203
214
|
const _getAwaiting = getAwaiting;
|
|
204
215
|
const _drainReturnReleases = drainReturnReleases;
|
|
205
216
|
const _pauseSpin = pauseSpin;
|
|
206
|
-
const
|
|
217
|
+
const _enqueueLock = traceSignals
|
|
218
|
+
? () => {
|
|
219
|
+
const progressed = enqueueLock();
|
|
220
|
+
if (progressed) {
|
|
221
|
+
dbg.log("signals", "work from=host");
|
|
222
|
+
}
|
|
223
|
+
return progressed;
|
|
224
|
+
}
|
|
225
|
+
: enqueueLock;
|
|
226
|
+
const _writeBatch = traceSignals
|
|
227
|
+
? (max) => {
|
|
228
|
+
const wrote = writeBatch(max);
|
|
229
|
+
if (wrote > 0)
|
|
230
|
+
dbg.log("signals", `result count=${wrote}`);
|
|
231
|
+
return wrote;
|
|
232
|
+
}
|
|
233
|
+
: writeBatch;
|
|
234
|
+
const _serviceBatchImmediate = traceSignals
|
|
235
|
+
? () => {
|
|
236
|
+
const ran = serviceBatchImmediate();
|
|
237
|
+
if (ran > 0)
|
|
238
|
+
dbg.log("signals", `run count=${ran}`);
|
|
239
|
+
return ran;
|
|
240
|
+
}
|
|
241
|
+
: serviceBatchImmediate;
|
|
242
|
+
const _pauseUntil = traceSignals
|
|
243
|
+
? (value, spinMicroseconds, parkMs) => {
|
|
244
|
+
dbg.log("signals", `idle token=${value}`);
|
|
245
|
+
pauseUntil(value, spinMicroseconds, parkMs);
|
|
246
|
+
}
|
|
247
|
+
: pauseUntil;
|
|
207
248
|
const loop = () => {
|
|
208
249
|
isInMacro = false;
|
|
209
250
|
let progressed = true;
|
|
@@ -234,8 +275,8 @@ export const workerMainLoop = async (startupData) => {
|
|
|
234
275
|
_pauseSpin();
|
|
235
276
|
continue;
|
|
236
277
|
}
|
|
237
|
-
_pauseUntil(
|
|
238
|
-
|
|
278
|
+
_pauseUntil(wakeToken, spinMicroseconds, parkMs);
|
|
279
|
+
wakeToken = a_load(opView, 0);
|
|
239
280
|
}
|
|
240
281
|
}
|
|
241
282
|
};
|
|
@@ -249,6 +290,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
249
290
|
}
|
|
250
291
|
port1Any.start?.();
|
|
251
292
|
port2.start?.();
|
|
293
|
+
dbg?.log("lifecycle", `ready: ${listOfFunctions.length} task(s) on thread ${thread}/${totalNumberOfThread}, entering dispatch loop`);
|
|
252
294
|
scheduleMacro();
|
|
253
295
|
};
|
|
254
296
|
const isWorkerGlobalScope = () => {
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { type SharedBufferSource } from "../../common/shared-buffer-region.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { LockBuffers } from "../../types.js";
|
|
3
3
|
type SharedMemoryBootData = {
|
|
4
4
|
sab: SharedBufferSource | undefined;
|
|
5
5
|
lock: LockBuffers | undefined;
|
|
6
6
|
returnLock: LockBuffers | undefined;
|
|
7
7
|
};
|
|
8
8
|
type ImportedFunctionsState = {
|
|
9
|
-
debug: DebugOptions | undefined;
|
|
10
9
|
list: string[];
|
|
11
10
|
ids: number[];
|
|
12
11
|
names?: string[];
|
|
13
12
|
listOfFunctions: readonly unknown[];
|
|
14
13
|
};
|
|
15
14
|
export declare const assertWorkerSharedMemoryBootData: ({ sab, lock, returnLock }: SharedMemoryBootData) => void;
|
|
16
|
-
export declare const assertWorkerImportsResolved: ({
|
|
15
|
+
export declare const assertWorkerImportsResolved: ({ list, ids, names, listOfFunctions }: ImportedFunctionsState) => void;
|
|
17
16
|
export {};
|
|
@@ -17,10 +17,7 @@ export const assertWorkerSharedMemoryBootData = ({ sab, lock, returnLock }) => {
|
|
|
17
17
|
throw new Error("worker missing return lock SABs");
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
-
export const assertWorkerImportsResolved = ({
|
|
21
|
-
if (debug?.logImportedUrl === true) {
|
|
22
|
-
console.log(list);
|
|
23
|
-
}
|
|
20
|
+
export const assertWorkerImportsResolved = ({ list, ids, names, listOfFunctions }) => {
|
|
24
21
|
if (listOfFunctions.length > 0 &&
|
|
25
22
|
(names === undefined || listOfFunctions.length === names.length))
|
|
26
23
|
return;
|
package/src/worker/timers.js
CHANGED
|
@@ -50,7 +50,7 @@ const isPlainNodeWindows = runtimeGlobals.process?.platform === "win32" &&
|
|
|
50
50
|
// full parkMs (up to seconds); the long park with no wake is what made CI
|
|
51
51
|
// runners appear to hang. The native wait already polls internally, so this is
|
|
52
52
|
// just bounding each poll slice.
|
|
53
|
-
const nativeWaitTimeoutMs = (parkMs) => isPlainNodeWindows ? 1 : parkMs
|
|
53
|
+
const nativeWaitTimeoutMs = (parkMs) => isPlainNodeWindows ? 1 : Number.isFinite(parkMs) ? parkMs : Infinity;
|
|
54
54
|
export const whilePausing = ({ pauseInNanoseconds }) => {
|
|
55
55
|
const forNanoseconds = pauseInNanoseconds ?? DEFAULT_PAUSE_TIME;
|
|
56
56
|
if (!a_pause || forNanoseconds <= 0)
|
|
@@ -101,10 +101,15 @@ export const sleepUntilChanged = ({ at, opView, pauseInNanoseconds, rxStatus, tx
|
|
|
101
101
|
else if (useSharedMemoryWait &&
|
|
102
102
|
a_wait &&
|
|
103
103
|
opView.buffer instanceof SharedArrayBuffer) {
|
|
104
|
-
|
|
104
|
+
// This is the notify-able path: a host Atomics.notify on opView wakes it.
|
|
105
|
+
// An infinite parkMs (debug) is intentional here — the worker blocks until
|
|
106
|
+
// a real signal instead of re-polling on a timeout.
|
|
107
|
+
a_wait(opView, at, value, parkMs ?? Infinity);
|
|
105
108
|
}
|
|
106
109
|
else if (a_wait && waitFallbackView) {
|
|
107
|
-
|
|
110
|
+
// waitFallbackView is never notified — this wait only ever times out and
|
|
111
|
+
// re-polls, so it must stay finite or the worker would hang forever.
|
|
112
|
+
a_wait(waitFallbackView, 0, 0, Number.isFinite(parkMs) ? parkMs : Infinity);
|
|
108
113
|
}
|
|
109
114
|
a_store(rxStatus, 0, 1);
|
|
110
115
|
};
|