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.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Worker-side debug handle. Lazily imported (see {@link ./gate.ts}) only when
3
+ * `KNITTING_DEBUG` names at least one namespace, so neither this code nor the
4
+ * baseline snapshot it takes exists when debug is off.
5
+ *
6
+ * Diagnostics go to stderr so they never corrupt a worker's stdout, and every
7
+ * line is tagged with the worker id, runtime, and a clock relative to when this
8
+ * worker's debug initialised. The clock is worker-local on purpose: a main-thread
9
+ * timestamp can't be compared against `performance.now()` here because the time
10
+ * origins differ across the thread/process boundary (it would read negative).
11
+ */
12
+ import { describeGlobalKey, diffGlobals, snapshotGlobals, } from "./env-diff.js";
13
+ export const initDebug = ({ name, runtime, namespaces }) => {
14
+ const all = namespaces.has("*");
15
+ const enabled = (namespace) => all || namespaces.has(namespace);
16
+ const base = performance.now();
17
+ const tag = `${name}·${runtime}`;
18
+ const log = (namespace, message) => {
19
+ if (!enabled(namespace))
20
+ return;
21
+ const elapsed = (performance.now() - base).toFixed(1);
22
+ console.error(`[${tag}·+${elapsed}ms] ${namespace}: ${message}`);
23
+ };
24
+ // Baseline for the environment diff, taken the moment debug initialises:
25
+ // before worker bootstrap and before any task module is imported. Only paid
26
+ // when `globals` tracing is actually on.
27
+ let previous = enabled("globals")
28
+ ? snapshotGlobals()
29
+ : undefined;
30
+ const envPhase = (label) => {
31
+ if (previous === undefined)
32
+ return;
33
+ const current = snapshotGlobals();
34
+ const { added, removed } = diffGlobals(previous, current);
35
+ previous = current;
36
+ if (added.length === 0 && removed.length === 0) {
37
+ log("globals", `${label}: no new globals`);
38
+ return;
39
+ }
40
+ if (added.length > 0) {
41
+ log("globals", `${label} +${added.length}: ${added.map(describeGlobalKey).join(" ")}`);
42
+ }
43
+ if (removed.length > 0) {
44
+ log("globals", `${label} -${removed.length}: ${removed.map(String).join(" ")}`);
45
+ }
46
+ };
47
+ return { enabled, log, envPhase };
48
+ };
@@ -1,6 +1,5 @@
1
1
  export type SignalArguments = ReturnType<typeof createSharedMemoryTransport>;
2
2
  import { type SharedBufferSource } from "../../common/shared-buffer-region.js";
3
- import { type DebugOptions } from "../../types.js";
4
3
  export declare const TRANSPORT_SIGNAL_BYTES: number;
5
4
  export type Sab = {
6
5
  size?: number;
@@ -10,11 +9,10 @@ type SignalForWorker = {
10
9
  sabObject?: Sab;
11
10
  isMain: boolean;
12
11
  thread: number;
13
- debug?: DebugOptions;
14
12
  startTime?: number;
15
13
  };
16
14
  export declare const createSharedMemoryTransport: ({ sabObject, isMain, startTime }: SignalForWorker) => {
17
- sab: import("../../types.js").SharedBufferRegion;
15
+ sab: import("../../common/shared-buffer-region.js").SharedBufferRegion;
18
16
  op: Int32Array<import("../../common/shared-buffer-region.js").SharedBuffer>;
19
17
  startAt: number;
20
18
  opView: Int32Array<import("../../common/shared-buffer-region.js").SharedBuffer>;
@@ -1,11 +1,20 @@
1
1
  import { type SharedBufferSource } from "../common/shared-buffer-region.js";
2
2
  export declare const PAYLOAD_DEFAULT_MAX_BYTE_LENGTH: number;
3
3
  export declare const PAYLOAD_DEFAULT_INITIAL_BYTES: number;
4
+ /** Payload backing mode. Growable uses SAB growth when the runtime supports it. */
4
5
  export type PayloadBufferMode = "growable" | "fixed";
5
6
  export type PayloadBufferOptions = {
7
+ /** Shared payload buffer mode. Defaults to growable when available. */
6
8
  mode?: PayloadBufferMode;
9
+ /** Initial shared payload bytes per direction. Defaults to 4 MiB when growable. */
7
10
  payloadInitialBytes?: number;
11
+ /** Shared payload growth cap. Defaults to 64 MiB. */
8
12
  payloadMaxByteLength?: number;
13
+ /**
14
+ * Max dynamic payload bytes for one call. Defaults to `payloadMaxByteLength >> 3`
15
+ * (about 8 MiB by default) and must stay `<= payloadMaxByteLength >> 3`.
16
+ * Over-cap payloads reject with `KNT_ERROR_3`.
17
+ */
9
18
  maxPayloadBytes?: number;
10
19
  };
11
20
  export type ResolvedPayloadBufferOptions = {
@@ -163,4 +163,4 @@ export declare const resolvePermissionProtocol: ({ permission, modules, }: {
163
163
  modules?: string[];
164
164
  }) => ResolvedPermissionProtocol | undefined;
165
165
  export declare const toRuntimePermissionFlags: (protocol: ResolvedPermissionProtocol | undefined) => string[];
166
- export type { PermissionPath, PermissionMode, PermissionLegacyMode, SysApiName, NodePermissionSettings, DenoPermissionSettings, PermissionEnvironment, PermissionProtocol, PermissionProtocolInput, ResolvedPermissionProtocol, };
166
+ export type { DenoPermissionSettings, NodePermissionSettings, PermissionEnvironment, PermissionLegacyMode, PermissionMode, PermissionPath, PermissionProtocol, PermissionProtocolInput, ResolvedPermissionProtocol, SysApiName, };
@@ -107,11 +107,14 @@ const normalizeSysApiList = (values) => {
107
107
  return out;
108
108
  };
109
109
  const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
110
- const normalizeProtocolInput = (input) => !input ? undefined : (typeof input === "string" ? { mode: input } : input);
110
+ const normalizeProtocolInput = (input) => !input
111
+ ? undefined
112
+ : (typeof input === "string" ? { mode: input } : input);
111
113
  const isWindows = () => {
112
114
  const nodeProcess = getNodeProcess();
113
- if (typeof nodeProcess?.platform === "string")
115
+ if (typeof nodeProcess?.platform === "string") {
114
116
  return nodeProcess.platform === "win32";
117
+ }
115
118
  const g = globalThis;
116
119
  return g.Deno?.build?.os === "windows";
117
120
  };
@@ -146,7 +149,8 @@ const getHome = () => {
146
149
  }
147
150
  const g = globalThis;
148
151
  try {
149
- const home = g.Deno?.env?.get?.("HOME") ?? g.Deno?.env?.get?.("USERPROFILE");
152
+ const home = g.Deno?.env?.get?.("HOME") ??
153
+ g.Deno?.env?.get?.("USERPROFILE");
150
154
  if (typeof home === "string" && home.length > 0)
151
155
  return home;
152
156
  }
@@ -198,7 +202,11 @@ const toPathList = (values, cwd, home) => {
198
202
  };
199
203
  const toUniquePathList = (values, cwd, home) => normalizeList(toPathList(values, cwd, home));
200
204
  const toEnvFiles = (input, cwd, home) => {
201
- const values = Array.isArray(input) ? input : input ? [input] : [DEFAULT_ENV_FILE];
205
+ const values = Array.isArray(input)
206
+ ? input
207
+ : input
208
+ ? [input]
209
+ : [DEFAULT_ENV_FILE];
202
210
  return toUniquePathList(values, cwd, home);
203
211
  };
204
212
  const rawRealpathSync = realpathSyncCompat.native ?? realpathSyncCompat;
@@ -212,7 +220,8 @@ const isPathWithin = (base, candidate) => {
212
220
  const canonicalBase = toCanonicalPath(base);
213
221
  const canonicalCandidate = toCanonicalPath(candidate);
214
222
  const relative = pathRelative(canonicalBase, canonicalCandidate);
215
- return relative === "" || (!relative.startsWith("..") && !pathIsAbsolute(relative));
223
+ return relative === "" ||
224
+ (!relative.startsWith("..") && !pathIsAbsolute(relative));
216
225
  };
217
226
  const defaultSensitiveProjectAndHomePaths = (cwd, home) => {
218
227
  const projectSensitive = DEFAULT_DENY_RELATIVE.map((entry) => pathResolve(cwd, entry));
@@ -343,6 +352,13 @@ const toDenoFlags = ({ read, readAll, write, writeAll, denyRead, denyWrite, net,
343
352
  flags.push(`--deny-env=${envDeny.join(",")}`);
344
353
  }
345
354
  for (const file of envFiles) {
355
+ try {
356
+ if (!existsSyncCompat(file))
357
+ continue;
358
+ }
359
+ catch {
360
+ continue;
361
+ }
346
362
  flags.push(`--env-file=${file}`);
347
363
  }
348
364
  if (runAll) {
@@ -476,9 +492,7 @@ export const resolvePermissionProtocol = ({ permission, modules, }) => {
476
492
  const configuredRead = readAll
477
493
  ? []
478
494
  : toPathList(Array.isArray(input.read) ? input.read : undefined, cwd, home);
479
- const configuredWrite = writeAll
480
- ? []
481
- : toPathList(Array.isArray(input.write) ? input.write : undefined, cwd, home);
495
+ const configuredWrite = writeAll ? [] : toPathList(Array.isArray(input.write) ? input.write : undefined, cwd, home);
482
496
  const resolvedRead = readAll
483
497
  ? []
484
498
  : hasExplicitRead
@@ -501,19 +515,17 @@ export const resolvePermissionProtocol = ({ permission, modules, }) => {
501
515
  : normalizeStringList(Array.isArray(input.net) ? input.net : []);
502
516
  const denyNet = normalizeStringList(input.denyNet);
503
517
  const allowImportAll = input.allowImport === true;
504
- const allowImport = allowImportAll
505
- ? []
506
- : normalizeStringList(Array.isArray(input.allowImport)
507
- ? input.allowImport
508
- : [...DEFAULT_ALLOW_IMPORT_HOSTS]);
518
+ const allowImport = allowImportAll ? [] : normalizeStringList(Array.isArray(input.allowImport)
519
+ ? input.allowImport
520
+ : [...DEFAULT_ALLOW_IMPORT_HOSTS]);
509
521
  const envAllowAll = input.env?.allow === true;
510
- const envAllow = envAllowAll
511
- ? []
512
- : normalizeStringList(Array.isArray(input.env?.allow) ? input.env.allow : []);
522
+ const envAllow = envAllowAll ? [] : normalizeStringList(Array.isArray(input.env?.allow) ? input.env.allow : []);
513
523
  const envDeny = normalizeStringList(input.env?.deny);
514
524
  const legacyRunEnabled = input.node?.allowChildProcess === true ||
515
525
  input.deno?.allowRun === true;
516
- const runSource = hasOwn(input, "run") ? input.run : (legacyRunEnabled ? true : []);
526
+ const runSource = hasOwn(input, "run")
527
+ ? input.run
528
+ : (legacyRunEnabled ? true : []);
517
529
  const runAll = runSource === true;
518
530
  const run = runAll
519
531
  ? []
@@ -526,9 +538,7 @@ export const resolvePermissionProtocol = ({ permission, modules, }) => {
526
538
  ? input.ffi
527
539
  : (input.node?.allowAddons === true ? true : false);
528
540
  const ffiAll = ffiSource === true;
529
- const ffi = ffiAll
530
- ? []
531
- : toUniquePathList(Array.isArray(ffiSource) ? ffiSource : undefined, cwd, home);
541
+ const ffi = ffiAll ? [] : toUniquePathList(Array.isArray(ffiSource) ? ffiSource : undefined, cwd, home);
532
542
  const denyFfi = toUniquePathList(input.denyFfi, cwd, home);
533
543
  const sysSource = input.sys;
534
544
  const sysAll = sysSource === true;
@@ -15,6 +15,11 @@ export declare const hostDispatcherLoop: ({ signalBox: { opView, txStatus, rxSta
15
15
  rerun: boolean;
16
16
  };
17
17
  };
18
+ type CheckWithState = (() => void) & {
19
+ isRunning: boolean;
20
+ rerun: boolean;
21
+ };
22
+ export type DispatcherCheck = CheckWithState;
18
23
  export declare class ChannelHandler {
19
24
  #private;
20
25
  channel: RuntimeMessageChannelLike;
@@ -32,3 +37,4 @@ export declare class ChannelHandler {
32
37
  */
33
38
  close(): void;
34
39
  }
40
+ export {};
@@ -1,10 +1,11 @@
1
1
  import { type ProcessSharedMemoryBacking } from "./process-worker.js";
2
2
  import { type Sab } from "../ipc/transport/shared-memory.js";
3
+ import { ChannelHandler, type DispatcherCheck } from "./dispatcher.js";
3
4
  import { lock2 } from "../memory/lock.js";
4
5
  import type { DebugOptions, DispatcherSettings, WorkerContext, WorkerData, WorkerSettings } from "../types.js";
5
6
  import "../worker/loop.js";
6
7
  import { type PayloadBufferOptions } from "../memory/payload-config.js";
7
- export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }: {
8
+ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug, hostDebug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, abortSignalCapacity, usesAbortSignal, sharedChannelHandler, }: {
8
9
  list: string[];
9
10
  ids: number[];
10
11
  names: string[];
@@ -12,6 +13,7 @@ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug
12
13
  sab?: Sab;
13
14
  thread: number;
14
15
  debug?: DebugOptions;
16
+ hostDebug?: (message: string) => void;
15
17
  totalNumberOfThread: number;
16
18
  source?: string;
17
19
  workerOptions?: WorkerSettings;
@@ -20,14 +22,18 @@ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug
20
22
  host?: DispatcherSettings;
21
23
  payload?: PayloadBufferOptions;
22
24
  bufferReferenceReturn?: "copy" | "borrow";
23
- payloadInitialBytes?: number;
24
- payloadMaxBytes?: number;
25
- bufferMode?: PayloadBufferOptions["mode"];
26
- maxPayloadBytes?: number;
27
25
  abortSignalCapacity?: number;
28
26
  usesAbortSignal?: boolean;
27
+ /**
28
+ * When set, this lane keeps its dispatcher state while the pool owns the
29
+ * macro channel that runs all lane checks.
30
+ */
31
+ sharedChannelHandler?: ChannelHandler;
29
32
  }) => WorkerContext & {
30
33
  lock: ReturnType<typeof lock2>;
31
34
  processSharedMemoryBackings?: readonly ProcessSharedMemoryBacking[];
35
+ dispatcherCheck?: DispatcherCheck;
36
+ laneWake?: () => void;
37
+ bindSend?: (fn: () => void) => void;
32
38
  };
33
39
  export type CreateContext = WorkerContext;
@@ -1,7 +1,7 @@
1
1
  import { createHostTxQueue } from "./tx-queue.js";
2
2
  import { cleanupProcessWorkerMemoryQuietly, createProcessSharedMemoryAllocator, createProcessWorkerMemoryLayout, createProcessWorkerNativeSignalNotifier, readProcessSharedMemorySettings, readProcessWorkerCommandPrefix, readProcessWorkerRuntime, serializeWorkerBootstrapData, spawnProcessWorker, terminateWorkerQuietly, toProcessWorkerBootPayload, toWorkerCompatExecArgv, toWorkerSafeExecArgv, } from "./process-worker.js";
3
3
  import { createSharedMemoryTransport, TRANSPORT_SIGNAL_BYTES, } from "../ipc/transport/shared-memory.js";
4
- import { ChannelHandler, hostDispatcherLoop } from "./dispatcher.js";
4
+ import { ChannelHandler, hostDispatcherLoop, } from "./dispatcher.js";
5
5
  import { HEADER_SLOT_STRIDE_U32, lock2, LOCK_SECTOR_BYTE_LENGTH, LockBound, } from "../memory/lock.js";
6
6
  import "../worker/loop.js";
7
7
  import { createSharedArrayBuffer, createWasmSharedArrayBuffer, } from "../common/runtime.js";
@@ -34,7 +34,7 @@ const withFixedPayloadConfig = (config) => ({
34
34
  mode: "fixed",
35
35
  payloadInitialBytes: config.payloadMaxByteLength,
36
36
  });
37
- export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }) => {
37
+ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, hostDebug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, abortSignalCapacity, usesAbortSignal, sharedChannelHandler, }) => {
38
38
  const tsFileUrl = new URL(import.meta.url);
39
39
  const poliWorker = RUNTIME_WORKER;
40
40
  const resolvedWorkerOptions = serializeWorkerBootstrapData(withDefaultWorkerTimers(workerOptions));
@@ -48,9 +48,6 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, total
48
48
  const processSharedMemorySettings = useProcessWorkerRuntime
49
49
  ? readProcessSharedMemorySettings(resolvedWorkerOptions)
50
50
  : undefined;
51
- if (debug?.logHref === true) {
52
- console.log(tsFileUrl);
53
- }
54
51
  if (!useProcessWorkerRuntime && typeof poliWorker !== "function") {
55
52
  throw new Error("Worker is not available in this runtime");
56
53
  }
@@ -63,15 +60,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, total
63
60
  return bytes > 0 ? bytes : undefined;
64
61
  };
65
62
  const basePayloadConfig = resolvePayloadBufferOptions({
66
- options: {
67
- ...payload,
68
- mode: payload?.mode ?? bufferMode,
69
- maxPayloadBytes: payload?.maxPayloadBytes ?? maxPayloadBytes,
70
- payloadInitialBytes: payload?.payloadInitialBytes ??
71
- sanitizeBytes(payloadInitialBytes),
72
- payloadMaxByteLength: payload?.payloadMaxByteLength ??
73
- sanitizeBytes(payloadMaxBytes),
74
- },
63
+ options: payload,
75
64
  });
76
65
  const resolvedPayloadConfig = useProcessWorkerRuntime
77
66
  ? withFixedPayloadConfig(basePayloadConfig)
@@ -183,7 +172,6 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, total
183
172
  : sab,
184
173
  isMain: true,
185
174
  thread,
186
- debug,
187
175
  });
188
176
  const signalBox = signals;
189
177
  const nativeNotifySignal = createProcessWorkerNativeSignalNotifier({
@@ -201,20 +189,54 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, total
201
189
  : undefined,
202
190
  });
203
191
  const { enqueue, rejectAll, txIdle, } = queue;
204
- const channelHandler = new ChannelHandler();
205
- const { check } = hostDispatcherLoop({
192
+ const thisSignal = signalBox.opView;
193
+ const a_add = Atomics.add;
194
+ const a_load = Atomics.load;
195
+ const a_notify = Atomics.notify;
196
+ const canNotifySignal = thisSignal.buffer instanceof SharedArrayBuffer;
197
+ const notifySignal = nativeNotifySignal ??
198
+ (canNotifySignal ? (() => a_notify(thisSignal, 0, 1)) : undefined);
199
+ // Wakes this lane's worker when it is parked (rxStatus 0). Used by send()
200
+ // (new work just arrived) — the dispatcher drain wakes it the same way.
201
+ const laneWake = () => {
202
+ if (a_load(signalBox.rxStatus, 0) === 0) {
203
+ a_add(thisSignal, 0, 1);
204
+ notifySignal?.();
205
+ }
206
+ };
207
+ let dispatchSend = () => { };
208
+ const send = () => dispatchSend();
209
+ let channelHandler;
210
+ const ownsChannel = sharedChannelHandler === undefined;
211
+ const ownChannel = sharedChannelHandler ?? new ChannelHandler();
212
+ const { check: dispatcherCheck } = hostDispatcherLoop({
206
213
  signalBox,
207
214
  queue,
208
- channelHandler,
215
+ channelHandler: ownChannel,
209
216
  dispatcherOptions: host,
210
217
  notifySignal: nativeNotifySignal,
211
- //thread,
212
- //debugSignal: debug?.logMain ?? false,
213
- //perf,
214
218
  });
215
- channelHandler.open(check);
219
+ if (ownsChannel) {
220
+ ownChannel.open(dispatcherCheck);
221
+ channelHandler = ownChannel;
222
+ dispatchSend = () => {
223
+ if (dispatcherCheck.isRunning === true)
224
+ return;
225
+ dispatcherCheck.isRunning = true;
226
+ Promise.resolve().then(dispatcherCheck);
227
+ laneWake();
228
+ };
229
+ }
216
230
  let worker;
217
231
  const workerUrl = source ?? tsFileUrl;
232
+ const workerMode = useProcessWorkerRuntime
233
+ ? "process"
234
+ : HAS_NODE_WORKER_THREADS
235
+ ? "worker_threads"
236
+ : "worker";
237
+ hostDebug?.(`worker thread=${thread} mode=${workerMode}` +
238
+ `${processWorkerRuntime ? ` runtime=${processWorkerRuntime}` : ""}` +
239
+ ` url=${String(workerUrl)}`);
218
240
  const workerDataPayload = {
219
241
  sab: signals.sab,
220
242
  abortSignalSAB,
@@ -308,7 +330,7 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, total
308
330
  return;
309
331
  closedReason = reason;
310
332
  rejectAll(reason);
311
- channelHandler.close();
333
+ channelHandler?.close();
312
334
  };
313
335
  const onWorkerMessage = (message) => {
314
336
  if (!isWorkerFatalMessage(message))
@@ -350,27 +372,6 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, total
350
372
  };
351
373
  }
352
374
  }
353
- const thisSignal = signalBox.opView;
354
- const a_add = Atomics.add;
355
- const a_load = Atomics.load;
356
- const a_notify = Atomics.notify;
357
- const canNotifySignal = thisSignal.buffer instanceof SharedArrayBuffer;
358
- const notifySignal = nativeNotifySignal ??
359
- (canNotifySignal ? (() => a_notify(thisSignal, 0, 1)) : undefined);
360
- //const scheduleFastCheck = queueMicrotask;
361
- const send = () => {
362
- if (check.isRunning === true)
363
- return;
364
- check.isRunning = true;
365
- Promise.resolve().then(check);
366
- // Macro lane: dispatcher check is driven by the channel callback.
367
- // channelHandler.notify();
368
- // Use opView as a wake counter in lock2 mode to avoid lost wakeups.
369
- if (a_load(signalBox.rxStatus, 0) === 0) {
370
- a_add(thisSignal, 0, 1);
371
- notifySignal?.();
372
- }
373
- };
374
375
  lock.setPromiseHandler((task, isRejected, value) => {
375
376
  queue.settlePromisePayload(task, isRejected, value);
376
377
  send();
@@ -393,6 +394,11 @@ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, total
393
394
  },
394
395
  lock,
395
396
  processSharedMemoryBackings: processSharedMemory?.backings,
397
+ dispatcherCheck,
398
+ laneWake: sharedChannelHandler !== undefined ? laneWake : undefined,
399
+ bindSend: sharedChannelHandler !== undefined
400
+ ? ((fn) => void (dispatchSend = fn))
401
+ : undefined,
396
402
  };
397
403
  return context;
398
404
  };
@@ -2,6 +2,7 @@ import { fileURLToPath as fileURLToPathCompat } from "node:url";
2
2
  import { HEADER_SLOT_STRIDE_U32, LOCK_SECTOR_BYTE_LENGTH, LockBound, } from "../memory/lock.js";
3
3
  import { createByteCarpet, getHeaderBlockByteLength, makeSharedBufferRegion, } from "../memory/byte-carpet.js";
4
4
  import { RUNTIME } from "../common/runtime.js";
5
+ import { debugHas } from "../debug/gate.js";
5
6
  import { RUNTIME_POOL_DEPTH, RUNTIME_POOL_DEPTH_ENV, RUNTIME_PROCESS_WORKER_BOOT_ENV, RUNTIME_PROCESS_WORKER_BOOT_VERSION, RUNTIME_PROCESS_WORKER_ENV, } from "../common/worker-runtime.js";
6
7
  import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
7
8
  import { toSharedBufferRegion, } from "../common/shared-buffer-region.js";
@@ -72,7 +73,7 @@ export const createProcessSharedMemoryAllocator = (debug) => {
72
73
  addon = loadNodeNativeAddon(require, "knitting_shared_memory");
73
74
  }
74
75
  catch (error) {
75
- if (debug?.extras === true) {
76
+ if (debugHas(debug, "lifecycle")) {
76
77
  console.warn("Process-shared memory allocator unavailable; falling back to SharedArrayBuffer.", error);
77
78
  }
78
79
  return undefined;
@@ -50,7 +50,11 @@ export function createHostTxQueue({ max, lock, returnLock, releaseBufferReferenc
50
50
  const completeFrame = releaseBufferReferenceReturn === undefined
51
51
  ? resolveReturn
52
52
  : () => withBufferReferenceReturnReleaser(releaseBufferReferenceReturn, resolveReturn);
53
- const txIdle = () => getPendingFrameCount() === 0 && inUsed === getPendingPromiseCount();
53
+ const hasActiveTasks = () => {
54
+ const count = (inUsed - getPendingPromiseCount()) | 0;
55
+ return count > 0;
56
+ };
57
+ const txIdle = () => getPendingFrameCount() === 0 && !hasActiveTasks();
54
58
  const rejectAll = (reason) => {
55
59
  for (let index = 0; index < queue.length; index++) {
56
60
  const slot = queue[index];
package/src/types.d.ts CHANGED
@@ -62,8 +62,11 @@ type Serializable = string | object | number | boolean | bigint;
62
62
  type ValidInput = bigint | void | JSONValue | symbol | ArrayBuffer | Uint8Array | Int32Array | Float64Array | BigInt64Array | BigUint64Array | DataView | Error | Date | Envelope<EnvelopeHeader, EnvelopeBody>;
63
63
  type Args = ValidInput | Serializable;
64
64
  type MaybePromise<T> = T | Promise<T>;
65
+ /** Blob payloads are not supported; pass ArrayBuffer/typed arrays instead. */
65
66
  type NoBlob<T> = T extends Blob ? never : T;
67
+ /** Task input may be a direct value or native Promise; thenables are plain values. */
66
68
  type TaskInput = NoBlob<Args> | Promise<NoBlob<Args>>;
69
+ /** Per-call timeout. A number is milliseconds; object form can return a default. */
67
70
  type TaskTimeout = number | {
68
71
  time: number;
69
72
  maybe?: true;
@@ -76,6 +79,7 @@ type BivariantCallback<Args extends unknown[], R> = {
76
79
  type AbortSignalConfig = {
77
80
  readonly hasAborted: true;
78
81
  };
82
+ /** `true` or config injects an abort toolkit as the task's second parameter. */
79
83
  type AbortSignalOption = true | AbortSignalConfig | undefined;
80
84
  type AbortSignalMethods<AS extends AbortSignalOption> = AS extends undefined ? never : {
81
85
  hasAborted: () => boolean;
@@ -94,7 +98,7 @@ type TaskLike<AS extends AbortSignalOption = AbortSignalOption> = {
94
98
  });
95
99
  type TaskFunctionLike = (...args: any[]) => any;
96
100
  type Composed<A extends TaskInput = Args, B extends Args = Args, AS extends AbortSignalOption = undefined> = FixPoint<A, B, AS> & SecondPart;
97
- type tasks = Record<string, Composed<any, any, AbortSignalOption> | TaskFunctionLike>;
101
+ type tasks = Record<string, Composed<any, any, any> | TaskFunctionLike>;
98
102
  type ComposedWithKey = Composed<any, any, AbortSignalOption> & {
99
103
  name: string;
100
104
  };
@@ -118,7 +122,9 @@ type FunctionMapType<T extends Record<string, TaskLike<any> | TaskFunctionLike>>
118
122
  [K in keyof T]: PromiseWrapped<TaskCallable<T[K]>, AbortSignalOfTask<T[K]>>;
119
123
  };
120
124
  interface FixPointBase<A extends TaskInput, B extends Args, AS extends AbortSignalOption = undefined> {
125
+ /** Worker function. It receives one value; use a tuple/object for many inputs. */
121
126
  readonly f: TaskFn<A, B, AS>;
127
+ /** Soft call timeout. Use `worker.hardTimeoutMs` for runaway CPU walls. */
122
128
  readonly timeout?: TaskTimeout;
123
129
  }
124
130
  type FixPoint<A extends TaskInput, B extends Args, AS extends AbortSignalOption = undefined> = FixPointBase<A, B, AS> & (AS extends undefined ? {
@@ -127,7 +133,9 @@ type FixPoint<A extends TaskInput, B extends Args, AS extends AbortSignalOption
127
133
  readonly abortSignal: AS;
128
134
  });
129
135
  type ImportTaskOptions<A extends TaskInput = void, B extends Args = void, AS extends AbortSignalOption = undefined> = Omit<FixPoint<A, B, AS>, "f"> & {
136
+ /** Module imported by the worker only. Relative paths resolve from caller. */
130
137
  readonly href: string;
138
+ /** Plain function export name. Defaults to `"default"`; do not target `task()`. */
131
139
  readonly name?: string;
132
140
  };
133
141
  type SecondPart = {
@@ -142,13 +150,22 @@ type SecondPart = {
142
150
  readonly imported?: boolean;
143
151
  };
144
152
  type SingleTaskPool<A extends TaskInput = Args, B extends Args = Args, AS extends AbortSignalOption = undefined> = {
153
+ /** Invoke the single task. Arguments may be native Promises. */
145
154
  call: PromiseWrapped<TaskFn<A, B, AS>, AS>;
155
+ /** Await worker teardown now. `using` disposes at scope exit without awaiting. */
146
156
  shutdown: (delayMs?: number) => Promise<void>;
157
+ /** Starts shutdown at scope exit. Use `shutdown()` when you must await it. */
147
158
  [Symbol.dispose]: () => void;
148
159
  };
149
160
  type Pool<T extends Record<string, TaskLike<any> | TaskFunctionLike>> = {
161
+ /** Await worker teardown now. `using` disposes at scope exit without awaiting. */
150
162
  shutdown: (delayMs?: number) => Promise<void>;
163
+ /** Starts shutdown at scope exit. Use `shutdown()` when you must await it. */
151
164
  [Symbol.dispose]: () => void;
165
+ /**
166
+ * Typed task callers. Each call accepts the task input or a native Promise.
167
+ * Thrown errors/rejections reject here as Error objects with cause chains.
168
+ */
152
169
  call: FunctionMapType<T>;
153
170
  };
154
171
  type ReturnFixed<A extends TaskInput = undefined, B extends Args = undefined, AS extends AbortSignalOption = undefined> = FixPoint<A, B, AS> & SecondPart & {
@@ -175,12 +192,13 @@ type Balancer = BalancerStrategy | {
175
192
  */
176
193
  strategy?: BalancerStrategy;
177
194
  };
178
- type DebugOptions = {
179
- extras?: boolean;
180
- logMain?: boolean;
181
- logHref?: boolean;
182
- logImportedUrl?: boolean;
195
+ /** Debug namespaces for host setup, worker state, imports, globals, and lifecycle. */
196
+ type DebugNamespace = "host" | "globals" | "signals" | "imports" | "lifecycle";
197
+ type DebugFlags = {
198
+ [Namespace in DebugNamespace]?: boolean;
183
199
  };
200
+ /** Pass `true` for all debug, or enable namespaces by name. */
201
+ type DebugOptions = boolean | DebugFlags;
184
202
  type WorkerBootstrapContext = {
185
203
  readonly thread: number;
186
204
  readonly totalNumberOfThread: number;
@@ -212,7 +230,7 @@ type WorkerSettings = {
212
230
  /**
213
231
  * Experimental worker runtime.
214
232
  * "thread" uses Worker/worker_threads. "process" spawns another JavaScript
215
- * runtime and shares one inherited fd-backed memory mapping.
233
+ * runtime, useful for process isolation, bwrap, and containers.
216
234
  */
217
235
  runtime?: "thread" | "process";
218
236
  /**
@@ -278,44 +296,34 @@ type DispatcherSettings = {
278
296
  * Max backoff delay (milliseconds).
279
297
  */
280
298
  maxBackoffMs?: number;
299
+ /**
300
+ * Host dispatcher topology.
301
+ * - `"per-thread"`: each worker owns its dispatcher and macro channel.
302
+ * - `"serial-channel"`: each worker keeps its own dispatcher check state, but
303
+ * one shared channel runs all lane checks from first to last.
304
+ *
305
+ * Experimental default: `"per-thread"` on Bun or with one worker, otherwise
306
+ * `"serial-channel"` for multi-worker Node/Deno pools. Can also be forced
307
+ * with the `KNITTING_DISPATCHER` env var (`serial-channel` or `per-thread`).
308
+ */
309
+ dispatcher?: "per-thread" | "serial-channel";
281
310
  };
282
311
  type CreatePool = {
312
+ /** Number of workers. Default: 1. */
283
313
  threads?: number;
284
- /**
285
- * @deprecated Too risky with processes, need to rewrite or delete.
286
- */
314
+ /** Add a host inline lane for regular tasks. Imported tasks still use workers. */
287
315
  inliner?: Inliner;
288
316
  balancer?: Balancer;
289
317
  worker?: WorkerSettings;
290
318
  /**
291
- * Payload transport settings.
319
+ * Payload transport settings. Default dynamic payload cap is about 8 MiB
320
+ * (`payloadMaxByteLength >> 3`, with a 64 MiB growth cap).
292
321
  */
293
322
  payload?: PayloadBufferOptions;
294
323
  /**
295
324
  * Experimental unsafe options.
296
325
  */
297
326
  unsafe?: UnsafeOptions;
298
- /**
299
- * Initial payload SharedArrayBuffer size (bytes) per worker direction.
300
- * Defaults to 4 MiB when growable SAB is available, otherwise defaults to
301
- * `payloadMaxBytes`.
302
- * @deprecated Use `payload.payloadInitialBytes`.
303
- */
304
- payloadInitialBytes?: number;
305
- /**
306
- * Maximum payload SharedArrayBuffer size (bytes) per worker direction.
307
- * Defaults to 64 MiB.
308
- * @deprecated Use `payload.payloadMaxByteLength`.
309
- */
310
- payloadMaxBytes?: number;
311
- /**
312
- * @deprecated Use `payload.mode`.
313
- */
314
- bufferMode?: PayloadBufferMode;
315
- /**
316
- * @deprecated Use `payload.maxPayloadBytes`.
317
- */
318
- maxPayloadBytes?: number;
319
327
  /**
320
328
  * Abort-aware signal pool capacity.
321
329
  * Defaults to `258`.
@@ -332,19 +340,18 @@ type CreatePool = {
332
340
  workerExecArgv?: string[];
333
341
  /**
334
342
  * Runtime permission protocol.
335
- * Omit to use strict defaults with `allowImport: true`.
343
+ * Omit to use strict defaults with `allowImport: true`; worker console is
344
+ * quiet unless `permission: { console: true }`.
345
+ *
346
+ * Task code cannot terminate the host: process/Deno exit APIs are blocked.
336
347
  * Use `"strict"` (default for object mode) or `"unsafe"`.
337
348
  * Accepts object form for fine-grained permission controls.
338
349
  */
339
350
  permission?: PermissionProtocolInput;
340
- /**
341
- * @deprecated Use `host` instead.
342
- */
343
- dispatcher?: DispatcherSettings;
344
351
  debug?: DebugOptions;
345
352
  source?: string;
346
353
  };
347
- export type { AbortSignalConfig as AbortSignalConfig, AbortSignalMethods as AbortSignalMethods, AbortSignalOption as AbortSignalOption, AbortSignalToolkit as AbortSignalToolkit, Args as Args, Balancer as Balancer, BalancerStrategy as BalancerStrategy, Composed as Composed, ComposedWithKey as ComposedWithKey, CreateContext as CreateContext, CreatePool as CreatePool, DebugOptions as DebugOptions, DispatcherSettings as DispatcherSettings, Envelope as Envelope, EnvelopeBody as EnvelopeBody, EnvelopeHeader as EnvelopeHeader, External as External, FixPoint as FixPoint, FunctionMapType as FunctionMapType, ImportTaskOptions as ImportTaskOptions, Inliner as Inliner, LockBuffers as LockBuffers, LockBufferTextCompat as LockBufferTextCompat, MaybePromise as MaybePromise, PayloadBufferMode as PayloadBufferMode, PayloadBufferOptions as PayloadBufferOptions, PermissionProtocol as PermissionProtocol, PermissionProtocolInput as PermissionProtocolInput, Pool as Pool, ProcessSharedMemoryMode as ProcessSharedMemoryMode, ProcessSharedMemorySettings as ProcessSharedMemorySettings, ResolvedPermissionProtocol as ResolvedPermissionProtocol, ReturnFixed as ReturnFixed, SecondPart as SecondPart, SharedBufferRegion as SharedBufferRegion, SharedBufferSource as SharedBufferSource, SharedBufferTextCompat as SharedBufferTextCompat, SingleTaskPool as SingleTaskPool, TaskFn as TaskFn, TaskInput as TaskInput, tasks as tasks, TaskTimeout as TaskTimeout, ValidInput as ValidInput, WorkerBootstrapContext as WorkerBootstrapContext, WorkerBootstrapFunction as WorkerBootstrapFunction, WorkerBootstrapOptions as WorkerBootstrapOptions, WorkerCall as WorkerCall, WorkerContext as WorkerContext, WorkerData as WorkerData, WorkerInvoke as WorkerInvoke, WorkerSettings as WorkerSettings, WorkerTimers as WorkerTimers, };
354
+ export type { AbortSignalConfig as AbortSignalConfig, AbortSignalMethods as AbortSignalMethods, AbortSignalOption as AbortSignalOption, AbortSignalToolkit as AbortSignalToolkit, Args as Args, Balancer as Balancer, BalancerStrategy as BalancerStrategy, Composed as Composed, ComposedWithKey as ComposedWithKey, CreateContext as CreateContext, CreatePool as CreatePool, DebugNamespace as DebugNamespace, DebugOptions as DebugOptions, DispatcherSettings as DispatcherSettings, Envelope as Envelope, EnvelopeBody as EnvelopeBody, EnvelopeHeader as EnvelopeHeader, External as External, FixPoint as FixPoint, FunctionMapType as FunctionMapType, ImportTaskOptions as ImportTaskOptions, Inliner as Inliner, LockBuffers as LockBuffers, LockBufferTextCompat as LockBufferTextCompat, MaybePromise as MaybePromise, PayloadBufferMode as PayloadBufferMode, PayloadBufferOptions as PayloadBufferOptions, PermissionProtocol as PermissionProtocol, PermissionProtocolInput as PermissionProtocolInput, Pool as Pool, ProcessSharedMemoryMode as ProcessSharedMemoryMode, ProcessSharedMemorySettings as ProcessSharedMemorySettings, ResolvedPermissionProtocol as ResolvedPermissionProtocol, ReturnFixed as ReturnFixed, SecondPart as SecondPart, SharedBufferRegion as SharedBufferRegion, SharedBufferSource as SharedBufferSource, SharedBufferTextCompat as SharedBufferTextCompat, SingleTaskPool as SingleTaskPool, TaskFn as TaskFn, TaskInput as TaskInput, tasks as tasks, TaskTimeout as TaskTimeout, ValidInput as ValidInput, WorkerBootstrapContext as WorkerBootstrapContext, WorkerBootstrapFunction as WorkerBootstrapFunction, WorkerBootstrapOptions as WorkerBootstrapOptions, WorkerCall as WorkerCall, WorkerContext as WorkerContext, WorkerData as WorkerData, WorkerInvoke as WorkerInvoke, WorkerSettings as WorkerSettings, WorkerTimers as WorkerTimers, };
348
355
  export type { Task as Task } from "./memory/lock.js";
349
356
  export { LockBound as LockBound, PayloadBuffer as PayloadBuffer, PayloadSignal as PayloadSignal, TaskIndex as TaskIndex, } from "./memory/lock.js";
350
357
  export type { RegisterMalloc as RegisterMalloc } from "./memory/regionRegistry.js";