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.
@@ -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]);
@@ -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 = timers?.parkMs ??
110
- Math.max(1, totalNumberOfThread) * 50;
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
- assertWorkerImportsResolved({ debug, list, ids, names, listOfFunctions });
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 wakeSeq = a_load(opView, 0);
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 _enqueueLock = enqueueLock;
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 _pauseUntil = pauseUntil;
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(wakeSeq, spinMicroseconds, parkMs);
238
- wakeSeq = a_load(opView, 0);
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 { DebugOptions, LockBuffers } from "../../types.js";
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: ({ debug, list, ids, names, listOfFunctions }: ImportedFunctionsState) => void;
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 = ({ debug, list, ids, names, listOfFunctions }) => {
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;
@@ -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 ?? 60;
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
- a_wait(opView, at, value, parkMs ?? 60);
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
- a_wait(waitFallbackView, 0, 0, parkMs ?? 1);
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
  };