knitting 0.1.56 → 0.1.61
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 +347 -46
- package/knitting.browser.d.ts +6 -0
- package/knitting.browser.js +1 -0
- package/knitting.d.ts +3 -2
- package/knitting.js +3 -3
- package/map.md +21 -9
- package/package.json +17 -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/scripts/build-compiled-worker.ts +276 -0
- package/scripts/compiled-worker/porffor.d.ts +5 -0
- package/scripts/compiled-worker/runtime.ts +529 -0
- package/scripts/compiled-worker/task-shim.ts +32 -0
- package/src/api.d.ts +24 -1
- package/src/api.js +311 -27
- package/src/common/module-url.d.ts +1 -0
- package/src/common/module-url.js +30 -2
- package/src/common/node-compat.d.ts +2 -0
- package/src/common/path-canonical.js +20 -6
- package/src/common/runtime.d.ts +3 -1
- package/src/common/runtime.js +25 -4
- package/src/common/task-source.d.ts +2 -0
- package/src/common/task-source.js +17 -1
- package/src/common/worker-runtime.js +16 -7
- package/src/connections/buffer-reference-native.js +25 -1
- package/src/connections/buffer-reference.d.ts +23 -2
- package/src/connections/buffer-reference.js +143 -29
- package/src/connections/bun.d.ts +1 -1
- package/src/connections/bun.js +25 -14
- package/src/connections/deno.d.ts +3 -1
- package/src/connections/deno.js +35 -14
- package/src/connections/external-array-buffer.d.ts +2 -0
- package/src/connections/external-array-buffer.js +33 -0
- package/src/connections/node-addons.d.ts +11 -0
- package/src/connections/node-addons.js +49 -1
- package/src/connections/node-ffi-api.d.ts +19 -0
- package/src/connections/node-ffi-api.js +28 -0
- package/src/connections/node-ffi.d.ts +27 -0
- package/src/connections/node-ffi.js +262 -0
- package/src/connections/node.d.ts +1 -1
- package/src/connections/node.js +20 -20
- package/src/connections/package-assets.js +25 -15
- package/src/connections/posix.d.ts +42 -0
- package/src/connections/posix.js +101 -0
- package/src/connections/process-shared-buffer.d.ts +1 -0
- package/src/connections/process-shared-buffer.js +11 -45
- package/src/connections/types.d.ts +4 -0
- package/src/connections/windows.d.ts +19 -10
- package/src/connections/windows.js +87 -66
- package/src/error.d.ts +7 -6
- package/src/error.js +8 -7
- package/src/memory/lock.d.ts +112 -77
- package/src/memory/lock.js +314 -87
- package/src/memory/payloadCodec.d.ts +11 -2
- package/src/memory/payloadCodec.js +129 -37
- package/src/memory/regionRegistry.d.ts +1 -3
- package/src/memory/regionRegistry.js +16 -14
- package/src/permission/compatibility.d.ts +28 -0
- package/src/permission/compatibility.js +239 -0
- package/src/permission/index.d.ts +2 -0
- package/src/permission/index.js +1 -0
- package/src/permission/protocol.d.ts +1 -0
- package/src/permission/protocol.js +59 -15
- package/src/runtime/compiled-artifact.d.ts +21 -0
- package/src/runtime/compiled-artifact.js +260 -0
- package/src/runtime/compiled-builder.d.ts +8 -0
- package/src/runtime/compiled-builder.js +42 -0
- package/src/runtime/compiled-worker.d.ts +27 -0
- package/src/runtime/compiled-worker.js +562 -0
- package/src/runtime/dispatcher.js +9 -0
- package/src/runtime/inline-executor.js +27 -15
- package/src/runtime/pool.d.ts +121 -5
- package/src/runtime/pool.js +263 -49
- package/src/runtime/process-worker.d.ts +3 -13
- package/src/runtime/process-worker.js +97 -101
- package/src/runtime/tx-queue.d.ts +18 -2
- package/src/runtime/tx-queue.js +56 -16
- package/src/runtime/worker-common.d.ts +13 -0
- package/src/runtime/worker-common.js +102 -0
- package/src/types.d.ts +89 -9
- package/src/worker/composable-runners.js +3 -2
- package/src/worker/loop.d.ts +1 -0
- package/src/worker/loop.js +52 -17
- package/src/worker/rx-queue.d.ts +11 -1
- package/src/worker/rx-queue.js +4 -1
- package/src/worker/task-loader.d.ts +5 -4
- package/src/worker/task-loader.js +13 -7
- package/src/worker/timers.d.ts +8 -1
- package/src/worker/timers.js +28 -23
|
@@ -49,7 +49,7 @@ const readSignal = (slot) => {
|
|
|
49
49
|
const signal = (encodedSignal - ABORT_SIGNAL_META_OFFSET) | 0;
|
|
50
50
|
return signal >= 0 ? signal : NO_ABORT_SIGNAL;
|
|
51
51
|
};
|
|
52
|
-
const makeToolkitCache = (hasAborted) => {
|
|
52
|
+
const makeToolkitCache = (hasAborted, now) => {
|
|
53
53
|
const bySignal = [];
|
|
54
54
|
return (signal) => {
|
|
55
55
|
let toolkit = bySignal[signal];
|
|
@@ -58,6 +58,7 @@ const makeToolkitCache = (hasAborted) => {
|
|
|
58
58
|
const hasAbortedMethod = () => hasAborted(signal);
|
|
59
59
|
toolkit = {
|
|
60
60
|
hasAborted: hasAbortedMethod,
|
|
61
|
+
now,
|
|
61
62
|
};
|
|
62
63
|
bySignal[signal] = toolkit;
|
|
63
64
|
return toolkit;
|
|
@@ -76,7 +77,7 @@ export const composeWorkerRunner = ({ job, timeout, hasAborted, now, }) => {
|
|
|
76
77
|
return applyTimeoutBudget(result, slot, timeout, nowTime);
|
|
77
78
|
};
|
|
78
79
|
}
|
|
79
|
-
const getToolkit = makeToolkitCache(hasAborted);
|
|
80
|
+
const getToolkit = makeToolkitCache(hasAborted, nowTime);
|
|
80
81
|
if (!timeout) {
|
|
81
82
|
return (slot) => {
|
|
82
83
|
const signal = readSignal(slot);
|
package/src/worker/loop.d.ts
CHANGED
package/src/worker/loop.js
CHANGED
|
@@ -4,9 +4,12 @@ import { isLockBufferTextCompat } from "../common/shared-buffer-text.js";
|
|
|
4
4
|
import { createWorkerRxQueue } from "./rx-queue.js";
|
|
5
5
|
import { createSharedMemoryTransport } from "../ipc/transport/shared-memory.js";
|
|
6
6
|
import { lock2 } from "../memory/lock.js";
|
|
7
|
+
// Side-effect import: registers the payload codec (cycle break for Andromeda;
|
|
8
|
+
// see lock.ts). Must run before any lock2() call.
|
|
9
|
+
import "../memory/payloadCodec.js";
|
|
7
10
|
import { getFunctions } from "./task-loader.js";
|
|
8
11
|
import { pauseGeneric, sleepUntilChanged, whilePausing } from "./timers.js";
|
|
9
|
-
import { RUNTIME, SET_IMMEDIATE } from "../common/runtime.js";
|
|
12
|
+
import { IS_ANDROMEDA, RUNTIME, SET_IMMEDIATE } from "../common/runtime.js";
|
|
10
13
|
import { getNodeProcess } from "../common/node-compat.js";
|
|
11
14
|
import { assertWorkerImportsResolved, assertWorkerSharedMemoryBootData, installPerformanceNowGuard, installTerminationGuard, installUnhandledRejectionSilencer, scrubWorkerDataSensitiveBuffers, } from "./safety/index.js";
|
|
12
15
|
import { signalAbortFactory } from "../shared/abortSignal.js";
|
|
@@ -70,7 +73,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
70
73
|
installTerminationGuard();
|
|
71
74
|
installUnhandledRejectionSilencer();
|
|
72
75
|
installPerformanceNowGuard();
|
|
73
|
-
const { debug, sab, thread, startAt, workerOptions, lock, returnLock, abortSignalSAB, abortSignalMax, payloadConfig, bufferReferenceReturn, permission, totalNumberOfThread, list, ids, names, at, } = startupData;
|
|
76
|
+
const { debug, sab, thread, startAt, workerOptions, lock, returnLock, abortSignalSAB, abortSignalMax, payloadConfig, bufferReferenceReturn, permission, totalNumberOfThread, list, ids, names, at, steal, } = startupData;
|
|
74
77
|
scrubWorkerDataSensitiveBuffers(startupData);
|
|
75
78
|
assertWorkerSharedMemoryBootData({ sab, lock, returnLock });
|
|
76
79
|
const debugNamespaces = resolveDebugNamespaces(debug);
|
|
@@ -81,10 +84,10 @@ export const workerMainLoop = async (startupData) => {
|
|
|
81
84
|
namespaces: debugNamespaces,
|
|
82
85
|
}))
|
|
83
86
|
: undefined;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
87
|
+
// const object, not `enum`: Andromeda's Nova engine can't parse `enum`.
|
|
88
|
+
const Comment = {
|
|
89
|
+
thisIsAHint: 0,
|
|
90
|
+
};
|
|
88
91
|
const signals = createSharedMemoryTransport({
|
|
89
92
|
sabObject: {
|
|
90
93
|
sharedSab: sab,
|
|
@@ -101,6 +104,12 @@ export const workerMainLoop = async (startupData) => {
|
|
|
101
104
|
payloadSector: lock.payloadSector,
|
|
102
105
|
payloadConfig,
|
|
103
106
|
textCompat: lock.textCompat,
|
|
107
|
+
processBoundary: RUNTIME_IS_PROCESS_WORKER,
|
|
108
|
+
// Under stealing the request region is shared, so decode() becomes a
|
|
109
|
+
// region-Dekker claim against the other endpoints.
|
|
110
|
+
consumers: steal?.consumers,
|
|
111
|
+
consumerId: steal?.consumerId,
|
|
112
|
+
regionLanes: steal?.regionLanes,
|
|
104
113
|
});
|
|
105
114
|
const returnLockState = lock2({
|
|
106
115
|
headers: returnLock.headers,
|
|
@@ -110,6 +119,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
110
119
|
payloadSector: returnLock.payloadSector,
|
|
111
120
|
payloadConfig,
|
|
112
121
|
textCompat: returnLock.textCompat,
|
|
122
|
+
processBoundary: RUNTIME_IS_PROCESS_WORKER,
|
|
113
123
|
});
|
|
114
124
|
const timers = workerOptions?.timers;
|
|
115
125
|
const spinMicroseconds = timers?.spinMicroseconds ??
|
|
@@ -158,6 +168,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
158
168
|
returnLock: returnLockState,
|
|
159
169
|
borrowReturnedBufferReferences: bufferReferenceReturn === "borrow",
|
|
160
170
|
hasAborted: abortSignals?.hasAborted,
|
|
171
|
+
stealing: steal !== undefined,
|
|
161
172
|
});
|
|
162
173
|
installBufferReferenceReleaseListener(releaseReturnedBufferReference);
|
|
163
174
|
a_store(rxStatus, 0, 1);
|
|
@@ -170,6 +181,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
170
181
|
pauseInNanoseconds: timers?.pauseNanoseconds,
|
|
171
182
|
enqueueLock,
|
|
172
183
|
write: () => hasCompleted() ? writeBatch(WRITE_MAX) : 0,
|
|
184
|
+
flushBeforeClaim: steal !== undefined,
|
|
173
185
|
nativeWaitU32,
|
|
174
186
|
useSharedMemoryWait: !(RUNTIME_IS_PROCESS_WORKER &&
|
|
175
187
|
RUNTIME === "node" &&
|
|
@@ -245,15 +257,30 @@ export const workerMainLoop = async (startupData) => {
|
|
|
245
257
|
pauseUntil(value, spinMicroseconds, parkMs);
|
|
246
258
|
}
|
|
247
259
|
: pauseUntil;
|
|
260
|
+
const flushBeforeClaim = steal !== undefined;
|
|
248
261
|
const loop = () => {
|
|
249
262
|
isInMacro = false;
|
|
250
263
|
let progressed = true;
|
|
251
264
|
let awaiting = 0;
|
|
252
265
|
while (true) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
266
|
+
if (flushBeforeClaim) {
|
|
267
|
+
// Stealing only. Flush finished work before taking more on: a computed
|
|
268
|
+
// response should not wait behind a claim, and that claim can block on
|
|
269
|
+
// a peer withdrawing its intent. Measured to hurt the per-lane path,
|
|
270
|
+
// where a claim is a cheap private decode and picking work up promptly
|
|
271
|
+
// matters more, so the classic order is kept below.
|
|
272
|
+
if (_hasCompleted()) {
|
|
273
|
+
if (_writeBatch(WRITE_MAX) > 0)
|
|
274
|
+
progressed = true;
|
|
275
|
+
}
|
|
276
|
+
progressed = _enqueueLock() || progressed;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
progressed = _enqueueLock();
|
|
280
|
+
if (_hasCompleted()) {
|
|
281
|
+
if (_writeBatch(WRITE_MAX) > 0)
|
|
282
|
+
progressed = true;
|
|
283
|
+
}
|
|
257
284
|
}
|
|
258
285
|
_drainReturnReleases();
|
|
259
286
|
if (_hasPending()) {
|
|
@@ -295,15 +322,23 @@ export const workerMainLoop = async (startupData) => {
|
|
|
295
322
|
};
|
|
296
323
|
const isWorkerGlobalScope = () => {
|
|
297
324
|
const scopeCtor = globalThis.WorkerGlobalScope;
|
|
298
|
-
if (typeof scopeCtor
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
325
|
+
if (typeof scopeCtor === "function") {
|
|
326
|
+
try {
|
|
327
|
+
if (globalThis instanceof scopeCtor) {
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// fall through to runtime-specific checks
|
|
333
|
+
}
|
|
303
334
|
}
|
|
304
|
-
|
|
305
|
-
|
|
335
|
+
// Andromeda has no `WorkerGlobalScope`; its worker scope carries a `self`
|
|
336
|
+
// global that the main thread lacks.
|
|
337
|
+
if (IS_ANDROMEDA &&
|
|
338
|
+
typeof globalThis.self !== "undefined") {
|
|
339
|
+
return true;
|
|
306
340
|
}
|
|
341
|
+
return false;
|
|
307
342
|
};
|
|
308
343
|
const isLockBuffers = (value) => {
|
|
309
344
|
if (!value || typeof value !== "object")
|
package/src/worker/rx-queue.d.ts
CHANGED
|
@@ -9,9 +9,19 @@ type ArgumentsForCreateWorkerQueue = {
|
|
|
9
9
|
borrowReturnedBufferReferences?: boolean;
|
|
10
10
|
hasAborted?: (signal: number) => boolean;
|
|
11
11
|
now?: () => number;
|
|
12
|
+
/**
|
|
13
|
+
* Work-stealing discipline: only claim when this worker has nothing left to
|
|
14
|
+
* run. Without it a worker keeps claiming whole regions on top of a backlog
|
|
15
|
+
* it has not started, which is exactly the hoarding stealing exists to
|
|
16
|
+
* prevent — the queued tasks would be better taken by an idle peer.
|
|
17
|
+
*
|
|
18
|
+
* Off for the classic per-lane path, where the host has already assigned
|
|
19
|
+
* these tasks to this lane and batching them is a win.
|
|
20
|
+
*/
|
|
21
|
+
stealing?: boolean;
|
|
12
22
|
};
|
|
13
23
|
export type CreateWorkerRxQueue = ReturnType<typeof createWorkerRxQueue>;
|
|
14
|
-
export declare const createWorkerRxQueue: ({ listOfFunctions, workerOptions, lock, returnLock, borrowReturnedBufferReferences, hasAborted, now, }: ArgumentsForCreateWorkerQueue) => {
|
|
24
|
+
export declare const createWorkerRxQueue: ({ listOfFunctions, workerOptions, lock, returnLock, borrowReturnedBufferReferences, hasAborted, now, stealing, }: ArgumentsForCreateWorkerQueue) => {
|
|
15
25
|
hasCompleted: () => boolean;
|
|
16
26
|
hasPending: () => boolean;
|
|
17
27
|
writeBatch: (max: number) => number;
|
package/src/worker/rx-queue.js
CHANGED
|
@@ -2,7 +2,7 @@ import RingQueue from "../ipc/tools/ring-queue.js";
|
|
|
2
2
|
import { attachPayloadTransportFinalizer, runTaskFinalizers, TaskFlag, TaskIndex, } from "../memory/lock.js";
|
|
3
3
|
import { composeWorkerRunner } from "./composable-runners.js";
|
|
4
4
|
import { BUFFER_REFERENCE_RETURN_RELEASE_TOKEN } from "../connections/buffer-reference.js";
|
|
5
|
-
export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, returnLock, borrowReturnedBufferReferences, hasAborted, now, }) => {
|
|
5
|
+
export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, returnLock, borrowReturnedBufferReferences, hasAborted, now, stealing, }) => {
|
|
6
6
|
const PLACE_HOLDER = (_) => {
|
|
7
7
|
throw ("UNREACHABLE FROM PLACE HOLDER (thread)");
|
|
8
8
|
};
|
|
@@ -71,6 +71,9 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
|
|
|
71
71
|
const { decode, resolved } = lock;
|
|
72
72
|
const resolvedShift = () => resolved.shiftNoClear();
|
|
73
73
|
const enqueueLock = () => {
|
|
74
|
+
// Steal only when idle: leaving work unclaimed lets a free peer take it.
|
|
75
|
+
if (stealing && toWork.size !== 0)
|
|
76
|
+
return false;
|
|
74
77
|
if (!decode())
|
|
75
78
|
return false;
|
|
76
79
|
let task = resolvedShift();
|
|
@@ -9,10 +9,11 @@ type GetFunctionParams = {
|
|
|
9
9
|
permission?: ResolvedPermissionProtocol;
|
|
10
10
|
};
|
|
11
11
|
type WorkerCallable = (args: unknown, abortToolkit?: unknown) => unknown;
|
|
12
|
-
export declare const
|
|
13
|
-
Reject
|
|
14
|
-
Resolve
|
|
15
|
-
}
|
|
12
|
+
export declare const TimeoutKind: {
|
|
13
|
+
readonly Reject: 0;
|
|
14
|
+
readonly Resolve: 1;
|
|
15
|
+
};
|
|
16
|
+
export type TimeoutKind = typeof TimeoutKind[keyof typeof TimeoutKind];
|
|
16
17
|
export type TimeoutSpec = {
|
|
17
18
|
ms: number;
|
|
18
19
|
kind: TimeoutKind;
|
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
import { endpointSymbol } from "../common/task-symbol.js";
|
|
2
|
-
import { toModuleUrl } from "../common/module-url.js";
|
|
2
|
+
import { toImportSpecifier, toModuleUrl } from "../common/module-url.js";
|
|
3
|
+
// const object, not `const enum`: Andromeda's Nova engine can't parse `enum`.
|
|
4
|
+
// No longer inlined, but only read on cold paths.
|
|
5
|
+
export const TimeoutKind = {
|
|
6
|
+
Reject: 0,
|
|
7
|
+
Resolve: 1,
|
|
8
|
+
};
|
|
3
9
|
const normalizeTimeout = (timeout) => {
|
|
4
10
|
if (timeout == null)
|
|
5
11
|
return undefined;
|
|
6
12
|
if (typeof timeout === "number") {
|
|
7
13
|
const ms = Math.floor(timeout);
|
|
8
14
|
return ms >= 0
|
|
9
|
-
? { ms, kind:
|
|
15
|
+
? { ms, kind: TimeoutKind.Reject, value: new Error("Task timeout") }
|
|
10
16
|
: undefined;
|
|
11
17
|
}
|
|
12
18
|
const ms = Math.floor(timeout.time);
|
|
13
19
|
if (!(ms >= 0))
|
|
14
20
|
return undefined;
|
|
15
21
|
if ("default" in timeout) {
|
|
16
|
-
return { ms, kind:
|
|
22
|
+
return { ms, kind: TimeoutKind.Resolve, value: timeout.default };
|
|
17
23
|
}
|
|
18
24
|
if (timeout.maybe === true) {
|
|
19
|
-
return { ms, kind:
|
|
25
|
+
return { ms, kind: TimeoutKind.Resolve, value: undefined };
|
|
20
26
|
}
|
|
21
27
|
if ("error" in timeout) {
|
|
22
|
-
return { ms, kind:
|
|
28
|
+
return { ms, kind: TimeoutKind.Reject, value: timeout.error };
|
|
23
29
|
}
|
|
24
|
-
return { ms, kind:
|
|
30
|
+
return { ms, kind: TimeoutKind.Reject, value: new Error("Task timeout") };
|
|
25
31
|
};
|
|
26
32
|
const composeWorkerCallable = (fixed, _permission) => {
|
|
27
33
|
return fixed.f;
|
|
@@ -30,7 +36,7 @@ export const getFunctions = async ({ list, ids, names, at, permission }) => {
|
|
|
30
36
|
const modules = list.map((specifier) => toModuleUrl(specifier));
|
|
31
37
|
const nameSet = new Set(names);
|
|
32
38
|
const results = await Promise.all(modules.map(async (imports) => {
|
|
33
|
-
const module = (await import(imports));
|
|
39
|
+
const module = (await import(toImportSpecifier(imports)));
|
|
34
40
|
const fixedTasks = Object.entries(module)
|
|
35
41
|
.filter(([_, value]) => value != null && typeof value === "object" &&
|
|
36
42
|
//@ts-ignore Reason -> trust me
|
package/src/worker/timers.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ type PauseOptions = {
|
|
|
4
4
|
export type NativeWaitU32 = (buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, expected: number, timeoutMs?: number) => unknown;
|
|
5
5
|
export declare const whilePausing: ({ pauseInNanoseconds }: PauseOptions) => () => void;
|
|
6
6
|
export declare const pauseGeneric: () => void;
|
|
7
|
-
export declare const sleepUntilChanged: ({ at, opView, pauseInNanoseconds, rxStatus, txStatus, enqueueLock, write, nativeWaitU32, useSharedMemoryWait, }: {
|
|
7
|
+
export declare const sleepUntilChanged: ({ at, opView, pauseInNanoseconds, rxStatus, txStatus, enqueueLock, write, nativeWaitU32, useSharedMemoryWait, flushBeforeClaim, }: {
|
|
8
8
|
opView: Int32Array;
|
|
9
9
|
rxStatus: Int32Array;
|
|
10
10
|
txStatus: Int32Array;
|
|
@@ -14,5 +14,12 @@ export declare const sleepUntilChanged: ({ at, opView, pauseInNanoseconds, rxSta
|
|
|
14
14
|
write?: () => number | boolean;
|
|
15
15
|
nativeWaitU32?: NativeWaitU32;
|
|
16
16
|
useSharedMemoryWait?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Stealing only: write results back before attempting to claim more work.
|
|
19
|
+
* A claim can spin waiting for a peer to withdraw its intent, so claiming
|
|
20
|
+
* first puts arbitration latency in front of a finished response. Measured
|
|
21
|
+
* to hurt the per-lane path, so it stays off by default.
|
|
22
|
+
*/
|
|
23
|
+
flushBeforeClaim?: boolean;
|
|
17
24
|
}) => (value: number, spinMicroseconds: number, parkMs?: number) => void;
|
|
18
25
|
export {};
|
package/src/worker/timers.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
1
|
+
// const object, not `enum`: Andromeda's Nova engine can't parse `enum`.
|
|
2
|
+
const Comment = {
|
|
3
|
+
thisIsAHint: 0,
|
|
4
|
+
};
|
|
5
5
|
const maybeGc = (() => {
|
|
6
6
|
const host = globalThis;
|
|
7
7
|
const gc = typeof host.gc === "function"
|
|
@@ -30,7 +30,9 @@ const a_load = Atomics.load;
|
|
|
30
30
|
const a_store = Atomics.store;
|
|
31
31
|
const a_wait = typeof Atomics.wait === "function" ? Atomics.wait : undefined;
|
|
32
32
|
const p_now = performance.now.bind(performance);
|
|
33
|
-
|
|
33
|
+
// `SharedArrayBuffer` is undeclared on pages that are not cross-origin
|
|
34
|
+
// isolated, and this module has to load anyway so the pool can report why.
|
|
35
|
+
const waitFallbackView = a_wait === undefined || typeof SharedArrayBuffer !== "function"
|
|
34
36
|
? undefined
|
|
35
37
|
: new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
36
38
|
const a_pause = "pause" in Atomics
|
|
@@ -51,6 +53,7 @@ const isPlainNodeWindows = runtimeGlobals.process?.platform === "win32" &&
|
|
|
51
53
|
// runners appear to hang. The native wait already polls internally, so this is
|
|
52
54
|
// just bounding each poll slice.
|
|
53
55
|
const nativeWaitTimeoutMs = (parkMs) => isPlainNodeWindows ? 1 : Number.isFinite(parkMs) ? parkMs : Infinity;
|
|
56
|
+
const pollingWaitTimeoutMs = (parkMs) => Number.isFinite(parkMs) ? Math.min(Math.max(parkMs, 0), 1) : 1;
|
|
54
57
|
export const whilePausing = ({ pauseInNanoseconds }) => {
|
|
55
58
|
const forNanoseconds = pauseInNanoseconds ?? DEFAULT_PAUSE_TIME;
|
|
56
59
|
if (!a_pause || forNanoseconds <= 0)
|
|
@@ -58,26 +61,27 @@ export const whilePausing = ({ pauseInNanoseconds }) => {
|
|
|
58
61
|
return () => a_pause(forNanoseconds);
|
|
59
62
|
};
|
|
60
63
|
export const pauseGeneric = whilePausing({});
|
|
61
|
-
export const sleepUntilChanged = ({ at, opView, pauseInNanoseconds, rxStatus, txStatus, enqueueLock, write, nativeWaitU32, useSharedMemoryWait = true, }) => {
|
|
64
|
+
export const sleepUntilChanged = ({ at, opView, pauseInNanoseconds, rxStatus, txStatus, enqueueLock, write, nativeWaitU32, useSharedMemoryWait = true, flushBeforeClaim = false, }) => {
|
|
62
65
|
const pause = pauseInNanoseconds === undefined
|
|
63
66
|
? pauseGeneric
|
|
64
67
|
: whilePausing({ pauseInNanoseconds });
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (wrote > 0)
|
|
73
|
-
progressed = true;
|
|
74
|
-
}
|
|
75
|
-
else if (wrote === true) {
|
|
76
|
-
progressed = true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return progressed;
|
|
68
|
+
const flushWrite = () => {
|
|
69
|
+
if (!write)
|
|
70
|
+
return false;
|
|
71
|
+
const wrote = write();
|
|
72
|
+
if (typeof wrote === "number")
|
|
73
|
+
return wrote > 0;
|
|
74
|
+
return wrote === true;
|
|
80
75
|
};
|
|
76
|
+
const tryProgress = flushBeforeClaim
|
|
77
|
+
? () => {
|
|
78
|
+
const wrote = flushWrite();
|
|
79
|
+
return enqueueLock() || wrote;
|
|
80
|
+
}
|
|
81
|
+
: () => {
|
|
82
|
+
const claimed = enqueueLock();
|
|
83
|
+
return flushWrite() || claimed;
|
|
84
|
+
};
|
|
81
85
|
return (value, spinMicroseconds, parkMs) => {
|
|
82
86
|
const until = p_now() + (spinMicroseconds / 1000);
|
|
83
87
|
maybeGc();
|
|
@@ -108,8 +112,9 @@ export const sleepUntilChanged = ({ at, opView, pauseInNanoseconds, rxStatus, tx
|
|
|
108
112
|
}
|
|
109
113
|
else if (a_wait && waitFallbackView) {
|
|
110
114
|
// waitFallbackView is never notified — this wait only ever times out and
|
|
111
|
-
// re-polls
|
|
112
|
-
|
|
115
|
+
// re-polls. Keep the slice short and finite so FFI-backed process workers
|
|
116
|
+
// stay responsive without a cross-process native wake primitive.
|
|
117
|
+
a_wait(waitFallbackView, 0, 0, pollingWaitTimeoutMs(parkMs));
|
|
113
118
|
}
|
|
114
119
|
a_store(rxStatus, 0, 1);
|
|
115
120
|
};
|