knitting 0.1.60 → 0.1.62
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/BROWSER.md +134 -0
- package/README.md +94 -6
- package/knitting.browser.d.ts +6 -0
- package/knitting.browser.js +1 -0
- package/package.json +11 -2
- package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
- package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +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_buffer_pointer.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/src/api.js +136 -25
- package/src/common/runtime.d.ts +2 -1
- package/src/common/runtime.js +8 -1
- package/src/common/task-source.d.ts +9 -0
- package/src/common/task-source.js +15 -0
- package/src/memory/lock.d.ts +37 -1
- package/src/memory/lock.js +262 -5
- package/src/memory/payloadCodec.js +2 -6
- package/src/memory/regionRegistry.d.ts +1 -3
- package/src/memory/regionRegistry.js +16 -14
- package/src/permission/protocol.js +5 -1
- package/src/runtime/dispatcher.d.ts +11 -8
- package/src/runtime/dispatcher.js +163 -21
- package/src/runtime/inline-executor.js +6 -1
- package/src/runtime/pool.d.ts +127 -4
- package/src/runtime/pool.js +262 -73
- package/src/runtime/process-worker.d.ts +24 -12
- package/src/runtime/process-worker.js +185 -115
- package/src/runtime/tx-queue.d.ts +17 -1
- package/src/runtime/tx-queue.js +120 -15
- package/src/runtime/worker-common.d.ts +13 -0
- package/src/runtime/worker-common.js +103 -0
- package/src/types.d.ts +75 -4
- package/src/worker/loop.js +34 -5
- package/src/worker/rx-queue.d.ts +11 -1
- package/src/worker/rx-queue.js +4 -1
- package/src/worker/timers.d.ts +8 -1
- package/src/worker/timers.js +20 -17
package/src/api.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { getCallerFilePath, getCallerHref, setModuleUrl, } from "./common/task-source.js";
|
|
2
2
|
import { DEBUG_ENABLED, resolveDebugNamespaces } from "./debug/gate.js";
|
|
3
|
-
import { genTaskID } from "./common/task-source.js";
|
|
3
|
+
import { genTaskID, stableTaskID } from "./common/task-source.js";
|
|
4
4
|
import { toModuleUrl } from "./common/module-url.js";
|
|
5
5
|
import { endpointSymbol } from "./common/task-symbol.js";
|
|
6
|
-
import { spawnWorkerContext } from "./runtime/pool.js";
|
|
7
|
-
import { ChannelHandler } from "./runtime/dispatcher.js";
|
|
8
|
-
import { RUNTIME } from "./common/runtime.js";
|
|
6
|
+
import { createStealPoolBuffers, MAX_STEAL_CONSUMERS, spawnWorkerContext, } from "./runtime/pool.js";
|
|
7
|
+
import { ChannelHandler, hostDispatcherLoop } from "./runtime/dispatcher.js";
|
|
8
|
+
import { createSharedArrayBuffer, RUNTIME } from "./common/runtime.js";
|
|
9
9
|
import { RUNTIME_IS_MAIN_THREAD, RUNTIME_POOL_DEPTH, RUNTIME_WORKER_DATA, } from "./common/worker-runtime.js";
|
|
10
10
|
import { classifyProcessPermissionCompatibility, enforceProcessPermissionCompatibility, resolvePermissionProtocol, toRuntimePermissionFlags, } from "./permission/index.js";
|
|
11
11
|
import { getNodeProcess } from "./common/node-compat.js";
|
|
12
|
-
import {
|
|
12
|
+
import { readProcessSharedMemorySettings, readProcessWorkerNodeMajor, readProcessWorkerRuntime, } from "./runtime/process-worker.js";
|
|
13
|
+
import { TRANSPORT_SIGNAL_BYTES } from "./ipc/transport/shared-memory.js";
|
|
13
14
|
import { inspectCompiledWorkerArtifact } from "./runtime/compiled-artifact.js";
|
|
14
15
|
import { managerMethod } from "./runtime/balancer.js";
|
|
15
16
|
import { createInlineExecutor } from "./runtime/inline-executor.js";
|
|
@@ -46,18 +47,6 @@ const readHostCwd = () => {
|
|
|
46
47
|
}
|
|
47
48
|
return undefined;
|
|
48
49
|
};
|
|
49
|
-
const readKnownProcessWorkerNodeMajor = (worker) => {
|
|
50
|
-
if (RUNTIME !== "node")
|
|
51
|
-
return undefined;
|
|
52
|
-
if (readProcessWorkerCommandPrefix(worker) !== undefined)
|
|
53
|
-
return undefined;
|
|
54
|
-
const nodeProcess = getNodeProcess();
|
|
55
|
-
if (nodeProcess?.env?.NODE_BINARY !== undefined)
|
|
56
|
-
return undefined;
|
|
57
|
-
const raw = nodeProcess?.versions?.node;
|
|
58
|
-
const major = Number.parseInt(String(raw).split(".", 1)[0] ?? "", 10);
|
|
59
|
-
return Number.isInteger(major) && major > 0 ? major : undefined;
|
|
60
|
-
};
|
|
61
50
|
const formatDebugList = (values, empty = "(none)") => values && values.length > 0 ? values.join(",") : empty;
|
|
62
51
|
const MAX_FUNCTION_ID = 0xFFFF;
|
|
63
52
|
const MAX_FUNCTION_COUNT = MAX_FUNCTION_ID + 1;
|
|
@@ -375,6 +364,21 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
375
364
|
hostDebug?.log(`permission=${permissionProtocol?.mode ?? "off"} execArgv=${formatDebugList(execArgv)}`);
|
|
376
365
|
const usesAbortSignal = listOfFunctions.some((fn) => fn.abortSignal !== undefined);
|
|
377
366
|
const resolvedWorker = resolveWorkerSettings(worker, callerHref);
|
|
367
|
+
const dispatcherEnv = nodeProcess?.env?.KNITTING_DISPATCHER;
|
|
368
|
+
const stealEnvRaw = nodeProcess?.env?.KNITTING_STEAL?.trim().toLowerCase();
|
|
369
|
+
const stealEnv = stealEnvRaw === "1" || stealEnvRaw === "true"
|
|
370
|
+
? true
|
|
371
|
+
: stealEnvRaw === "0" || stealEnvRaw === "false"
|
|
372
|
+
? false
|
|
373
|
+
: undefined;
|
|
374
|
+
// Shared-submit stealing is the default for compatible multi-worker pools.
|
|
375
|
+
// An explicit option wins over the environment; compiled and inline
|
|
376
|
+
// transports retain their existing topology.
|
|
377
|
+
const dispatcherExplicitlySelected = host?.dispatcher !== undefined ||
|
|
378
|
+
dispatcherEnv === "serial-channel" || dispatcherEnv === "per-thread";
|
|
379
|
+
const stealDefaultCompatible = balancer === undefined &&
|
|
380
|
+
!dispatcherExplicitlySelected && (threads ?? 1) <= MAX_STEAL_CONSUMERS;
|
|
381
|
+
const stealRequested = host?.steal ?? stealEnv ?? stealDefaultCompatible;
|
|
378
382
|
const usingCompiledWorker = resolvedWorker?.runtime === "compiled";
|
|
379
383
|
if (resolvedWorker?.compiled !== undefined && !usingCompiledWorker) {
|
|
380
384
|
throw new Error("worker.compiled requires worker.runtime to be compiled");
|
|
@@ -437,7 +441,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
437
441
|
target: {
|
|
438
442
|
runtime: processRuntime,
|
|
439
443
|
nodeMajor: processRuntime === "node"
|
|
440
|
-
?
|
|
444
|
+
? readProcessWorkerNodeMajor(resolvedWorker)
|
|
441
445
|
: undefined,
|
|
442
446
|
},
|
|
443
447
|
}));
|
|
@@ -454,7 +458,6 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
454
458
|
`(import { isMain } from "knitting") so only the main program starts ` +
|
|
455
459
|
`the pool.`);
|
|
456
460
|
}
|
|
457
|
-
const dispatcherEnv = nodeProcess?.env?.KNITTING_DISPATCHER;
|
|
458
461
|
const explicitDispatcher = host?.dispatcher ??
|
|
459
462
|
(dispatcherEnv === "serial-channel" || dispatcherEnv === "per-thread"
|
|
460
463
|
? dispatcherEnv
|
|
@@ -470,10 +473,37 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
470
473
|
return "serial-channel";
|
|
471
474
|
})();
|
|
472
475
|
const dispatcher = explicitDispatcher ?? autoDispatcher;
|
|
473
|
-
|
|
476
|
+
// Work stealing: one shared submit region, private return lanes, one
|
|
477
|
+
// pool-global pending registry. It is the compatible multi-worker default;
|
|
478
|
+
// an explicit balancer/dispatcher keeps the private-lane topology unless
|
|
479
|
+
// stealing itself was explicitly requested.
|
|
480
|
+
const useSteal = stealRequested &&
|
|
481
|
+
!usingCompiledWorker &&
|
|
482
|
+
(threads ?? 1) > 1 &&
|
|
483
|
+
!usingInliner;
|
|
484
|
+
const stealBuffers = useSteal
|
|
485
|
+
? createStealPoolBuffers({
|
|
486
|
+
threads: threads ?? 1,
|
|
487
|
+
payload,
|
|
488
|
+
regionLanes: host?.stealRegionLanes,
|
|
489
|
+
abortSignalCapacity,
|
|
490
|
+
usesAbortSignal,
|
|
491
|
+
processWorker: resolvedWorker?.runtime === "process"
|
|
492
|
+
? {
|
|
493
|
+
signalBytes: TRANSPORT_SIGNAL_BYTES,
|
|
494
|
+
sharedMemory: readProcessSharedMemorySettings(resolvedWorker),
|
|
495
|
+
}
|
|
496
|
+
: undefined,
|
|
497
|
+
})
|
|
498
|
+
: undefined;
|
|
499
|
+
const serialChannel = !usingCompiledWorker && !useSteal &&
|
|
474
500
|
dispatcher === "serial-channel";
|
|
501
|
+
// One serial-channel hop drives every lane check in turn, and it depends on
|
|
502
|
+
// the channel's delivery to do that: swapping in the cheaper `setImmediate`
|
|
503
|
+
// pump cost 25% throughput and tripled p99 here, where every other topology
|
|
504
|
+
// gained. See ChannelHandler's note.
|
|
475
505
|
const serialDispatcherChannel = serialChannel
|
|
476
|
-
? new ChannelHandler()
|
|
506
|
+
? new ChannelHandler("channel")
|
|
477
507
|
: undefined;
|
|
478
508
|
let workers = Array.from({
|
|
479
509
|
length: threads ?? 1,
|
|
@@ -496,14 +526,85 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
496
526
|
usesAbortSignal,
|
|
497
527
|
permission: permissionProtocol,
|
|
498
528
|
sharedChannelHandler: serialDispatcherChannel,
|
|
529
|
+
stealPool: stealBuffers === undefined ? undefined : {
|
|
530
|
+
submitBuffers: stealBuffers.submitBuffers,
|
|
531
|
+
returnBuffers: stealBuffers.returnBuffers[thread],
|
|
532
|
+
hostSubmitLock: stealBuffers.hostSubmitLock,
|
|
533
|
+
hostReturnLock: stealBuffers.hostReturnLocks[thread],
|
|
534
|
+
sharedQueue: stealBuffers.sharedQueue,
|
|
535
|
+
consumers: threads ?? 1,
|
|
536
|
+
consumerId: thread,
|
|
537
|
+
regionLanes: stealBuffers.regionLanes,
|
|
538
|
+
abortSignalSAB: stealBuffers.abortSignalSAB,
|
|
539
|
+
abortSignalMax: stealBuffers.abortSignalMax,
|
|
540
|
+
processMemory: stealBuffers.processMemory,
|
|
541
|
+
},
|
|
499
542
|
}));
|
|
543
|
+
const stealChannel = useSteal ? new ChannelHandler() : undefined;
|
|
544
|
+
if (useSteal) {
|
|
545
|
+
const channel = stealChannel;
|
|
546
|
+
const queue = stealBuffers.sharedQueue;
|
|
547
|
+
// Wake exactly one worker per drain, round-robin. Stealing decouples the
|
|
548
|
+
// wake target from the assignment: if the woken worker is busy, any other
|
|
549
|
+
// idle endpoint claims the region instead. So this costs one notify per
|
|
550
|
+
// publish regardless of pool size, rather than one per lane.
|
|
551
|
+
let wakeCursor = 0;
|
|
552
|
+
const wakeOne = () => {
|
|
553
|
+
const lanes = workers.length;
|
|
554
|
+
if (lanes === 0)
|
|
555
|
+
return;
|
|
556
|
+
const lane = wakeCursor;
|
|
557
|
+
wakeCursor = wakeCursor + 1 < lanes ? wakeCursor + 1 : 0;
|
|
558
|
+
workers[lane].laneWake?.();
|
|
559
|
+
};
|
|
560
|
+
// The shared dispatcher drives one queue, so it needs signal words of its
|
|
561
|
+
// own rather than any single lane's; waking is delegated to `wakeOne`.
|
|
562
|
+
const signalWords = new Int32Array(createSharedArrayBuffer(3 * Int32Array.BYTES_PER_ELEMENT));
|
|
563
|
+
const { check } = hostDispatcherLoop({
|
|
564
|
+
signalBox: {
|
|
565
|
+
opView: signalWords.subarray(0, 1),
|
|
566
|
+
txStatus: signalWords.subarray(1, 2),
|
|
567
|
+
rxStatus: signalWords.subarray(2, 3),
|
|
568
|
+
},
|
|
569
|
+
queue,
|
|
570
|
+
channelHandler: channel,
|
|
571
|
+
dispatcherOptions: host,
|
|
572
|
+
notifySignal: wakeOne,
|
|
573
|
+
crossProcess: resolvedWorker?.runtime === "process",
|
|
574
|
+
});
|
|
575
|
+
channel.open(check);
|
|
576
|
+
workers.forEach((context) => {
|
|
577
|
+
context.bindSend(() => {
|
|
578
|
+
if (check.isRunning !== true) {
|
|
579
|
+
check.isRunning = true;
|
|
580
|
+
Promise.resolve().then(check);
|
|
581
|
+
}
|
|
582
|
+
wakeOne();
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
hostDebug?.log(`dispatcher=steal lanes=${workers.length} g=${stealBuffers.regionLanes}`);
|
|
586
|
+
}
|
|
500
587
|
const sharedDispatcherChannel = serialDispatcherChannel;
|
|
501
588
|
if (serialChannel) {
|
|
502
589
|
const channel = serialDispatcherChannel;
|
|
503
590
|
const checks = workers.map((context) => context.dispatcherCheck);
|
|
591
|
+
const laneIdle = workers.map((context) => context.txIdle);
|
|
504
592
|
let serialScheduled = false;
|
|
505
593
|
let serialInFlight = false;
|
|
506
594
|
let serialRerun = false;
|
|
595
|
+
// Busy-lane set: a tick walks only the lanes that have work, not all of
|
|
596
|
+
// them. `active` is a dense list of lane indices; `isActive` keeps the
|
|
597
|
+
// membership test O(1). A lane joins on send() and leaves once its queue
|
|
598
|
+
// reports idle, so an N-worker pool with one busy lane costs one check per
|
|
599
|
+
// tick instead of N.
|
|
600
|
+
const active = [];
|
|
601
|
+
const isActive = new Uint8Array(checks.length);
|
|
602
|
+
const markActive = (lane) => {
|
|
603
|
+
if (isActive[lane] === 1)
|
|
604
|
+
return;
|
|
605
|
+
isActive[lane] = 1;
|
|
606
|
+
active.push(lane);
|
|
607
|
+
};
|
|
507
608
|
const runSerialChecks = () => {
|
|
508
609
|
if (serialInFlight) {
|
|
509
610
|
serialRerun = true;
|
|
@@ -513,11 +614,19 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
513
614
|
do {
|
|
514
615
|
serialRerun = false;
|
|
515
616
|
serialScheduled = false;
|
|
516
|
-
|
|
517
|
-
|
|
617
|
+
// Walk backwards so dropping an idle lane is a swap-with-last pop.
|
|
618
|
+
for (let index = active.length - 1; index >= 0; index--) {
|
|
619
|
+
const lane = active[index];
|
|
620
|
+
const check = checks[lane];
|
|
518
621
|
if (check.isRunning !== true)
|
|
519
622
|
check.isRunning = true;
|
|
520
623
|
check();
|
|
624
|
+
if (laneIdle[lane]()) {
|
|
625
|
+
isActive[lane] = 0;
|
|
626
|
+
const last = active.pop();
|
|
627
|
+
if (index < active.length)
|
|
628
|
+
active[index] = last;
|
|
629
|
+
}
|
|
521
630
|
}
|
|
522
631
|
} while (serialRerun);
|
|
523
632
|
serialInFlight = false;
|
|
@@ -533,9 +642,10 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
533
642
|
Promise.resolve().then(runSerialChecks);
|
|
534
643
|
};
|
|
535
644
|
channel.open(runSerialChecks);
|
|
536
|
-
workers.forEach((context) => {
|
|
645
|
+
workers.forEach((context, lane) => {
|
|
537
646
|
const wake = context.laneWake;
|
|
538
647
|
context.bindSend(() => {
|
|
648
|
+
markActive(lane);
|
|
539
649
|
scheduleSerialCheck();
|
|
540
650
|
wake();
|
|
541
651
|
});
|
|
@@ -580,6 +690,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
580
690
|
closePromise = Promise.allSettled(workers.map((context) => context.kills()))
|
|
581
691
|
.then(() => {
|
|
582
692
|
sharedDispatcherChannel?.close();
|
|
693
|
+
stealChannel?.close();
|
|
583
694
|
});
|
|
584
695
|
return closePromise;
|
|
585
696
|
};
|
|
@@ -738,7 +849,7 @@ const buildTaskDefinitionFromCaller = (input, callerHref, at, imported = false)
|
|
|
738
849
|
const importedFrom = new URL(callerHref).href;
|
|
739
850
|
const out = ({
|
|
740
851
|
...input,
|
|
741
|
-
id:
|
|
852
|
+
id: stableTaskID(importedFrom, at),
|
|
742
853
|
importedFrom,
|
|
743
854
|
at,
|
|
744
855
|
imported,
|
package/src/common/runtime.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
type RuntimeName = "deno" | "bun" | "node" | "andromeda" | "unknown";
|
|
1
|
+
type RuntimeName = "deno" | "bun" | "node" | "andromeda" | "browser" | "unknown";
|
|
2
2
|
export declare const IS_DENO: boolean;
|
|
3
3
|
export declare const IS_BUN: boolean;
|
|
4
4
|
export declare const IS_NODE: boolean;
|
|
5
5
|
export declare const IS_ANDROMEDA: boolean;
|
|
6
|
+
export declare const IS_BROWSER: boolean;
|
|
6
7
|
export declare const RUNTIME: RuntimeName;
|
|
7
8
|
export declare const SET_IMMEDIATE: typeof setImmediate & ((cb: () => void) => void);
|
|
8
9
|
export declare const HAS_SHARED_WASM_MEMORY: boolean;
|
package/src/common/runtime.js
CHANGED
|
@@ -7,6 +7,11 @@ export const IS_NODE = typeof nodeProcess?.versions?.node === "string";
|
|
|
7
7
|
// Andromeda (Nova engine): no `node:` builtins or `WebAssembly`; detect via
|
|
8
8
|
// its private bootstrap global.
|
|
9
9
|
export const IS_ANDROMEDA = typeof globals.__andromeda__ !== "undefined";
|
|
10
|
+
// A page or a web worker: no `node:` builtins, but `Worker`, `MessageChannel`,
|
|
11
|
+
// and a cross-origin isolated `SharedArrayBuffer` are all the pool needs.
|
|
12
|
+
export const IS_BROWSER = !IS_DENO && !IS_BUN && !IS_NODE && !IS_ANDROMEDA &&
|
|
13
|
+
(typeof globals.document !== "undefined" ||
|
|
14
|
+
typeof globals.WorkerGlobalScope === "function");
|
|
10
15
|
export const RUNTIME = (IS_DENO
|
|
11
16
|
? "deno"
|
|
12
17
|
: IS_BUN
|
|
@@ -15,7 +20,9 @@ export const RUNTIME = (IS_DENO
|
|
|
15
20
|
? "node"
|
|
16
21
|
: IS_ANDROMEDA
|
|
17
22
|
? "andromeda"
|
|
18
|
-
:
|
|
23
|
+
: IS_BROWSER
|
|
24
|
+
? "browser"
|
|
25
|
+
: "unknown");
|
|
19
26
|
// `WebAssembly` may be undeclared (not just undefined) on Andromeda, where a
|
|
20
27
|
// bare reference throws; capture it through `globalThis` so access is safe.
|
|
21
28
|
const WebAssemblyRef = globals.WebAssembly;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export declare const genTaskID: () => number;
|
|
2
|
+
/**
|
|
3
|
+
* Identity of a task, stable across processes.
|
|
4
|
+
*
|
|
5
|
+
* `genTaskID` numbers tasks by the order their module happened to be evaluated,
|
|
6
|
+
* which host and worker do not share: the host imports in its own source order
|
|
7
|
+
* and the worker imports in task-name order. `(href, at)` is the same pair in
|
|
8
|
+
* both, because `at` counts `task()` calls within one module.
|
|
9
|
+
*/
|
|
10
|
+
export declare const stableTaskID: (href: string, at: number) => number;
|
|
2
11
|
export declare const setModuleUrl: (url: string | undefined) => void;
|
|
3
12
|
export declare const getModuleUrlOverride: () => string | undefined;
|
|
4
13
|
export declare const getCallerHref: (offset?: number) => string;
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { toModuleUrl } from "./module-url.js";
|
|
2
2
|
export const genTaskID = ((counter) => () => counter++)(0);
|
|
3
|
+
/**
|
|
4
|
+
* Identity of a task, stable across processes.
|
|
5
|
+
*
|
|
6
|
+
* `genTaskID` numbers tasks by the order their module happened to be evaluated,
|
|
7
|
+
* which host and worker do not share: the host imports in its own source order
|
|
8
|
+
* and the worker imports in task-name order. `(href, at)` is the same pair in
|
|
9
|
+
* both, because `at` counts `task()` calls within one module.
|
|
10
|
+
*/
|
|
11
|
+
export const stableTaskID = (href, at) => {
|
|
12
|
+
let hash = 0x811c9dc5;
|
|
13
|
+
for (let index = 0; index < href.length; index++) {
|
|
14
|
+
hash = Math.imul(hash ^ href.charCodeAt(index), 0x01000193);
|
|
15
|
+
}
|
|
16
|
+
return (Math.imul(hash ^ at, 0x01000193) >>> 0);
|
|
17
|
+
};
|
|
3
18
|
const INTERNAL_CALLER_HINTS = [
|
|
4
19
|
"/src/common/task-source.ts",
|
|
5
20
|
"/src/common/task-source.js",
|
package/src/memory/lock.d.ts
CHANGED
|
@@ -159,6 +159,22 @@ export declare const HEADER_SLOT_STRIDE_BYTES: number;
|
|
|
159
159
|
export declare const HEADER_TASK_LINE_U32: number;
|
|
160
160
|
export declare const HEADER_STATIC_PAYLOAD_U32: number;
|
|
161
161
|
export declare const HEADER_TASK_OFFSET_IN_SLOT_U32: number;
|
|
162
|
+
export declare const STEAL_ACK_SLOT_OFFSET_U32: number;
|
|
163
|
+
export declare const STEAL_WANT_SLOT_OFFSET_U32: number;
|
|
164
|
+
/**
|
|
165
|
+
* The payload allocator needs its own participant-owned ACK per consumer, since
|
|
166
|
+
* several workers can free payload slots from one shared allocator. No intent
|
|
167
|
+
* word: freeing needs no arbitration, a consumer only frees what it drained.
|
|
168
|
+
*/
|
|
169
|
+
export declare const STEAL_PAYLOAD_ACK_SLOT_OFFSET_U32: number;
|
|
170
|
+
/**
|
|
171
|
+
* Host-owned liveness mask for stealing consumers. A dead claimant may leave
|
|
172
|
+
* its WANT word set forever; survivors ignore WANT from consumers whose bit is
|
|
173
|
+
* clear here. This preserves single-writer ownership of every WANT word.
|
|
174
|
+
*/
|
|
175
|
+
export declare const STEAL_LIVE_SLOT_OFFSET_U32: number;
|
|
176
|
+
/** Host-owned arm word for the return-lock completion doorbell. */
|
|
177
|
+
export declare const DOORBELL_ARMED_SLOT_OFFSET_U32: number;
|
|
162
178
|
export declare const HEADER_U32_LENGTH: number;
|
|
163
179
|
export declare const HEADER_BYTE_LENGTH: number;
|
|
164
180
|
export declare const makeTask: () => Task;
|
|
@@ -177,7 +193,12 @@ type ResolveHostOptions = {
|
|
|
177
193
|
* - encode/decode are not re-entrant; payload codec uses a shared scratch buffer.
|
|
178
194
|
*/
|
|
179
195
|
export type Lock2 = ReturnType<typeof lock2>;
|
|
180
|
-
export
|
|
196
|
+
export type WaitAsyncState = "not-equal" | "ok" | "timed-out";
|
|
197
|
+
export type WaitAsyncResult = {
|
|
198
|
+
async: boolean;
|
|
199
|
+
value: WaitAsyncState | PromiseLike<WaitAsyncState>;
|
|
200
|
+
};
|
|
201
|
+
export declare const lock2: ({ headers, headerSlotStrideU32, LockBoundSector, payload, payloadConfig, payloadSector, textCompat, resultList, toSentList, recycleList, processBoundary, consumers, consumerId, regionLanes, notifyOnHostPublish, }: {
|
|
181
202
|
headers?: SharedBufferSource;
|
|
182
203
|
headerSlotStrideU32?: number;
|
|
183
204
|
LockBoundSector?: SharedBufferSource;
|
|
@@ -189,6 +210,18 @@ export declare const lock2: ({ headers, headerSlotStrideU32, LockBoundSector, pa
|
|
|
189
210
|
resultList?: RingQueue<Task>;
|
|
190
211
|
recycleList?: RingQueue<Task>;
|
|
191
212
|
processBoundary?: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Number of consumer endpoints sharing this lock. `1` (default) keeps the
|
|
215
|
+
* classic single-consumer path. `> 1` enables region-Dekker work stealing and
|
|
216
|
+
* is only valid on a host->worker submit lock, never on a return lock.
|
|
217
|
+
*/
|
|
218
|
+
consumers?: number;
|
|
219
|
+
/** This endpoint's id, `0..consumers-1`. Priority is fixed by id. */
|
|
220
|
+
consumerId?: number;
|
|
221
|
+
/** Lanes claimed per Dekker handshake. Paper rule: `slots / regionLanes >= consumers + 1`. */
|
|
222
|
+
regionLanes?: number;
|
|
223
|
+
/** Notify a host-side wait after this endpoint publishes a frame. */
|
|
224
|
+
notifyOnHostPublish?: boolean;
|
|
192
225
|
}) => {
|
|
193
226
|
enlist: (task: Task) => true;
|
|
194
227
|
encode: (task: Task, state?: number) => boolean;
|
|
@@ -203,10 +236,13 @@ export declare const lock2: ({ headers, headerSlotStrideU32, LockBoundSector, pa
|
|
|
203
236
|
workerBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
|
|
204
237
|
recyclecList: RingQueue<Task>;
|
|
205
238
|
resolveHost: ({ queue, onResolved, shouldSettle, activeRejectPlaceholder, }: ResolveHostOptions) => () => number;
|
|
239
|
+
waitForHostChange: (timeoutMs?: number) => WaitAsyncResult | undefined;
|
|
240
|
+
setHostWaiterArmed: (armed: boolean) => void;
|
|
206
241
|
hasPendingFrames: () => boolean;
|
|
207
242
|
getPendingFrameCount: () => number;
|
|
208
243
|
getPendingPromiseCount: () => number;
|
|
209
244
|
resetPendingState: () => void;
|
|
245
|
+
deactivateStealConsumer: (id: number) => boolean;
|
|
210
246
|
takeDeferredCount: () => number;
|
|
211
247
|
setPromiseHandler: (handler?: PromisePayloadHandler) => void;
|
|
212
248
|
};
|