knitting 0.1.63 → 0.1.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +525 -335
- package/knitting.browser.js +1 -1
- package/map.md +0 -6
- package/package.json +3 -3
- package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_doorbell.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_doorbell.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_doorbell.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_doorbell.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_doorbell.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-native-addons.ts +5 -0
- package/shared-memory.d.ts +3 -0
- package/shared-memory.js +3 -0
- package/src/api.js +105 -42
- package/src/common/with-resolvers.js +2 -5
- package/src/common/worker-runtime.d.ts +7 -0
- package/src/common/worker-runtime.js +7 -0
- package/src/connections/buffer-reference.d.ts +10 -36
- package/src/connections/buffer-reference.js +15 -170
- package/src/connections/node-addons.d.ts +1 -1
- package/src/connections/shared-array-buffer-payload.d.ts +7 -0
- package/src/connections/shared-array-buffer-payload.js +27 -11
- package/src/knitting_buffer_pointer.cc +57 -2
- package/src/knitting_doorbell.cc +220 -0
- package/src/memory/knitting-body.d.ts +44 -0
- package/src/memory/knitting-body.js +51 -0
- package/src/memory/knitting-buffer-http.d.ts +116 -0
- package/src/memory/knitting-buffer-http.js +255 -0
- package/src/memory/knitting-buffer.d.ts +250 -0
- package/src/memory/knitting-buffer.js +695 -0
- package/src/memory/lazy-region-registry.d.ts +83 -0
- package/src/memory/lazy-region-registry.js +355 -0
- package/src/memory/lock.d.ts +38 -15
- package/src/memory/lock.js +205 -79
- package/src/memory/payloadCodec.d.ts +18 -2
- package/src/memory/payloadCodec.js +309 -65
- package/src/memory/regionRegistry.d.ts +6 -0
- package/src/memory/regionRegistry.js +125 -240
- package/src/memory/shared-buffer-io.d.ts +7 -0
- package/src/memory/shared-buffer-io.js +34 -8
- package/src/runtime/deno-doorbell.d.ts +26 -0
- package/src/runtime/deno-doorbell.js +117 -0
- package/src/runtime/dispatcher.d.ts +8 -6
- package/src/runtime/dispatcher.js +80 -58
- package/src/runtime/host-arg-arena.d.ts +3 -0
- package/src/runtime/host-arg-arena.js +16 -0
- package/src/runtime/node-doorbell.d.ts +14 -0
- package/src/runtime/node-doorbell.js +84 -0
- package/src/runtime/pool.d.ts +21 -15
- package/src/runtime/pool.js +104 -116
- package/src/runtime/process-worker.d.ts +9 -0
- package/src/runtime/process-worker.js +22 -2
- package/src/runtime/tx-queue.d.ts +2 -5
- package/src/runtime/tx-queue.js +52 -48
- package/src/types.d.ts +35 -71
- package/src/worker/loop.js +79 -57
- package/src/worker/rx-queue.d.ts +2 -3
- package/src/worker/rx-queue.js +34 -40
- package/src/worker/shared-return.d.ts +9 -0
- package/src/worker/shared-return.js +22 -0
- package/src/worker/task-loader.js +1 -2
- package/src/worker/timers.d.ts +2 -6
- package/src/worker/timers.js +14 -19
- package/unsafe.d.ts +2 -1
- package/unsafe.js +2 -1
package/src/worker/rx-queue.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
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
|
-
|
|
5
|
-
|
|
4
|
+
/** Tasks run per `serviceBatchImmediate` call before returning to the loop. */
|
|
5
|
+
const SERVICE_BATCH_MAX = 32;
|
|
6
|
+
export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, returnLock, hasAborted, now, stealing, }) => {
|
|
6
7
|
const PLACE_HOLDER = (_) => {
|
|
7
8
|
throw ("UNREACHABLE FROM PLACE HOLDER (thread)");
|
|
8
9
|
};
|
|
9
|
-
let hasAnythingFinished = 0;
|
|
10
10
|
let awaiting = 0;
|
|
11
11
|
const jobs = listOfFunctions.reduce((acc, fixed) => (acc.push(fixed.run), acc), []);
|
|
12
12
|
const toWork = new RingQueue();
|
|
@@ -26,12 +26,7 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
|
|
|
26
26
|
const returnHostBits = returnLock.hostBits;
|
|
27
27
|
const returnWorkerBits = returnLock.workerBits;
|
|
28
28
|
const deferredReleases = [];
|
|
29
|
-
const
|
|
30
|
-
const drainReturnReleases = () => {
|
|
31
|
-
if (deferredReleases.length === 0)
|
|
32
|
-
return;
|
|
33
|
-
if ((a_load(returnHostBits, 0) ^ a_load(returnWorkerBits, 0)) !== 0)
|
|
34
|
-
return;
|
|
29
|
+
const releaseDeferredReturns = () => {
|
|
35
30
|
for (let i = 0; i < deferredReleases.length; i++) {
|
|
36
31
|
try {
|
|
37
32
|
deferredReleases[i]();
|
|
@@ -42,18 +37,17 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
|
|
|
42
37
|
}
|
|
43
38
|
deferredReleases.length = 0;
|
|
44
39
|
};
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const release = explicitReturnReleases.get(key);
|
|
48
|
-
if (release === undefined)
|
|
40
|
+
const drainReturnReleases = () => {
|
|
41
|
+
if (deferredReleases.length === 0)
|
|
49
42
|
return;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
43
|
+
// `returnHostBits` is the publication word of the return lock, and only
|
|
44
|
+
// this thread ever writes it (encode() toggles hostBits, the host toggles
|
|
45
|
+
// workerBits). Reading our own word plainly skips an `Atomics.load`, which
|
|
46
|
+
// is a non-inlined call an order of magnitude dearer than a plain read;
|
|
47
|
+
// the peer's word still needs the atomic.
|
|
48
|
+
if ((returnHostBits[0] ^ a_load(returnWorkerBits, 0)) !== 0)
|
|
49
|
+
return;
|
|
50
|
+
releaseDeferredReturns();
|
|
57
51
|
};
|
|
58
52
|
const runByIndex = listOfFunctions.reduce((acc, fixed, idx) => {
|
|
59
53
|
const job = jobs[idx];
|
|
@@ -65,9 +59,13 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
|
|
|
65
59
|
}));
|
|
66
60
|
return acc;
|
|
67
61
|
}, []);
|
|
62
|
+
// A settled task is either written into the return lock straight away or
|
|
63
|
+
// parked in `pendingFrames`, so the queue's own size already answers "is
|
|
64
|
+
// there anything left to flush" — the separate counter this used to keep was
|
|
65
|
+
// an increment and a decrement per task tracking a value it could read.
|
|
68
66
|
const hasCompleted = workerOptions?.resolveAfterFinishingAll === true
|
|
69
|
-
? () =>
|
|
70
|
-
: () =>
|
|
67
|
+
? () => pendingFrames.size !== 0 && toWork.size === 0
|
|
68
|
+
: () => pendingFrames.size !== 0;
|
|
71
69
|
const { decode, resolved } = lock;
|
|
72
70
|
const resolvedShift = () => resolved.shiftNoClear();
|
|
73
71
|
const enqueueLock = () => {
|
|
@@ -86,34 +84,22 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
|
|
|
86
84
|
}
|
|
87
85
|
return true;
|
|
88
86
|
};
|
|
89
|
-
const
|
|
90
|
-
if (!returnLock.encode(slot))
|
|
91
|
-
return false;
|
|
92
|
-
return true;
|
|
93
|
-
};
|
|
87
|
+
const returnEncode = returnLock.encode;
|
|
94
88
|
const sendReturn = (slot, shouldReject) => {
|
|
95
89
|
slot[IDX_FLAGS] = shouldReject ? FLAG_REJECT : 0;
|
|
96
|
-
if (!
|
|
90
|
+
if (!returnEncode(slot))
|
|
97
91
|
return false;
|
|
98
92
|
// Preserve return finalizers past slot recycle; release after drain.
|
|
99
93
|
if (slot.finalize !== undefined) {
|
|
100
|
-
|
|
101
|
-
if (token === undefined || borrowReturnedBufferReferences !== true) {
|
|
102
|
-
deferredReleases.push(slot.finalize);
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
explicitReturnReleases.set(token.toString(), slot.finalize);
|
|
106
|
-
}
|
|
94
|
+
deferredReleases.push(slot.finalize);
|
|
107
95
|
slot.finalize = undefined;
|
|
108
96
|
}
|
|
109
|
-
hasAnythingFinished--;
|
|
110
97
|
recyclePush(slot);
|
|
111
98
|
return true;
|
|
112
99
|
};
|
|
113
100
|
const settleNow = (slot, isError, value, wasAwaited) => {
|
|
114
101
|
runTaskFinalizers(slot);
|
|
115
102
|
slot.value = value;
|
|
116
|
-
hasAnythingFinished++;
|
|
117
103
|
if (wasAwaited && awaiting > 0)
|
|
118
104
|
awaiting--;
|
|
119
105
|
const shouldReject = isError ||
|
|
@@ -145,14 +131,20 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
|
|
|
145
131
|
},
|
|
146
132
|
serviceBatchImmediate: () => {
|
|
147
133
|
let processed = 0;
|
|
148
|
-
|
|
134
|
+
// Every trip back out to the dispatch loop costs a claim attempt and the
|
|
135
|
+
// per-pass bookkeeping, so a low cap makes that scaffolding a per-task
|
|
136
|
+
// tax on cheap tasks. Run a longer run when the return lock is keeping
|
|
137
|
+
// up, and cut it short the moment a settle lands in `pendingFrames` --
|
|
138
|
+
// that means the return region is full and the right move is to go
|
|
139
|
+
// flush it rather than pile up more finished work.
|
|
140
|
+
while (processed < SERVICE_BATCH_MAX && toWork.size !== 0) {
|
|
149
141
|
const slot = toWorkShift();
|
|
150
142
|
try {
|
|
151
143
|
const fnIndex = slot[TaskIndex.FunctionID] & FUNCTION_ID_MASK;
|
|
152
144
|
const result = runByIndex[fnIndex](slot);
|
|
153
145
|
slot[IDX_FLAGS] = 0;
|
|
154
|
-
slot.value = null;
|
|
155
146
|
if (result instanceof Promise) {
|
|
147
|
+
slot.value = null;
|
|
156
148
|
awaiting++;
|
|
157
149
|
result.then((value) => settleNow(slot, false, value, true), (err) => settleNow(slot, true, err, true));
|
|
158
150
|
}
|
|
@@ -164,12 +156,14 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
|
|
|
164
156
|
settleNow(slot, true, err, false);
|
|
165
157
|
}
|
|
166
158
|
++processed;
|
|
159
|
+
if (pendingFrames.size !== 0)
|
|
160
|
+
break;
|
|
167
161
|
}
|
|
168
162
|
return processed;
|
|
169
163
|
},
|
|
170
164
|
enqueueLock,
|
|
171
165
|
drainReturnReleases,
|
|
172
|
-
|
|
166
|
+
releaseDeferredReturns,
|
|
173
167
|
hasAwaiting: () => awaiting > 0,
|
|
174
168
|
getAwaiting: () => awaiting,
|
|
175
169
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Point `sharedBytes` at the arena of `payload`, this worker's return lane. */
|
|
2
|
+
export declare const installSharedReturn: (payload: object) => void;
|
|
3
|
+
export declare const resetSharedReturn: () => void;
|
|
4
|
+
/**
|
|
5
|
+
* Allocate an uninitialized borrowed return buffer. The caller must write every
|
|
6
|
+
* byte it returns and must not retain the view beyond the borrow window.
|
|
7
|
+
* Ordinary allocation is used when borrowing is unavailable.
|
|
8
|
+
*/
|
|
9
|
+
export declare const sharedBytes: (byteLength: number, zeroFill?: boolean) => Uint8Array;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getSharedReturnAllocator } from "../memory/payloadCodec.js";
|
|
2
|
+
// `sharedBytes` uses the worker's return-lane arena; outside that context it
|
|
3
|
+
// falls back to an ordinary Uint8Array.
|
|
4
|
+
let allocate;
|
|
5
|
+
/** Point `sharedBytes` at the arena of `payload`, this worker's return lane. */
|
|
6
|
+
export const installSharedReturn = (payload) => {
|
|
7
|
+
allocate = getSharedReturnAllocator(payload);
|
|
8
|
+
};
|
|
9
|
+
export const resetSharedReturn = () => {
|
|
10
|
+
allocate = undefined;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Allocate an uninitialized borrowed return buffer. The caller must write every
|
|
14
|
+
* byte it returns and must not retain the view beyond the borrow window.
|
|
15
|
+
* Ordinary allocation is used when borrowing is unavailable.
|
|
16
|
+
*/
|
|
17
|
+
export const sharedBytes = (byteLength, zeroFill = false) => {
|
|
18
|
+
if (!Number.isInteger(byteLength) || byteLength < 0) {
|
|
19
|
+
throw new RangeError("sharedBytes(byteLength) requires a non-negative integer");
|
|
20
|
+
}
|
|
21
|
+
return allocate?.(byteLength, zeroFill) ?? new Uint8Array(byteLength);
|
|
22
|
+
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { endpointSymbol } from "../common/task-symbol.js";
|
|
2
2
|
import { toImportSpecifier, toModuleUrl } from "../common/module-url.js";
|
|
3
|
-
//
|
|
4
|
-
// No longer inlined, but only read on cold paths.
|
|
3
|
+
// Use a const object; Andromeda's Nova engine cannot parse `enum`.
|
|
5
4
|
export const TimeoutKind = {
|
|
6
5
|
Reject: 0,
|
|
7
6
|
Resolve: 1,
|
package/src/worker/timers.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ type PauseOptions = {
|
|
|
2
2
|
pauseInNanoseconds?: number;
|
|
3
3
|
};
|
|
4
4
|
export type NativeWaitU32 = (buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, expected: number, timeoutMs?: number) => unknown;
|
|
5
|
+
export declare const IS_BUN_WINDOWS: boolean;
|
|
5
6
|
export declare const whilePausing: ({ pauseInNanoseconds }: PauseOptions) => () => void;
|
|
6
7
|
export declare const pauseGeneric: () => void;
|
|
7
8
|
export declare const sleepUntilChanged: ({ at, opView, pauseInNanoseconds, rxStatus, txStatus, enqueueLock, write, nativeWaitU32, useSharedMemoryWait, flushBeforeClaim, }: {
|
|
@@ -14,12 +15,7 @@ export declare const sleepUntilChanged: ({ at, opView, pauseInNanoseconds, rxSta
|
|
|
14
15
|
write?: () => number | boolean;
|
|
15
16
|
nativeWaitU32?: NativeWaitU32;
|
|
16
17
|
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
|
-
*/
|
|
18
|
+
/** Stealing only: flush results before claiming more work. */
|
|
23
19
|
flushBeforeClaim?: boolean;
|
|
24
20
|
}) => (value: number, spinMicroseconds: number, parkMs?: number) => void;
|
|
25
21
|
export {};
|
package/src/worker/timers.js
CHANGED
|
@@ -30,8 +30,7 @@ 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
|
-
//
|
|
34
|
-
// isolated, and this module has to load anyway so the pool can report why.
|
|
33
|
+
// Some pages do not define SharedArrayBuffer; keep this module loadable there.
|
|
35
34
|
const waitFallbackView = a_wait === undefined || typeof SharedArrayBuffer !== "function"
|
|
36
35
|
? undefined
|
|
37
36
|
: new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
@@ -43,17 +42,16 @@ const isPlainNodeWindows = runtimeGlobals.process?.platform === "win32" &&
|
|
|
43
42
|
typeof runtimeGlobals.process?.versions?.node === "string" &&
|
|
44
43
|
runtimeGlobals.process?.versions?.bun === undefined &&
|
|
45
44
|
runtimeGlobals.Deno === undefined;
|
|
46
|
-
// Windows
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// just bounding each poll slice.
|
|
45
|
+
// Bun on Windows requires special handling for short waits and process rings.
|
|
46
|
+
export const IS_BUN_WINDOWS = runtimeGlobals.process?.platform === "win32" &&
|
|
47
|
+
typeof runtimeGlobals.process?.versions?.bun === "string";
|
|
48
|
+
// Bun on Windows busy-spins for waits of 1ms or less; use a 2ms minimum.
|
|
49
|
+
const BUN_WINDOWS_MIN_WAIT_MS = 2;
|
|
50
|
+
const floorBunWindowsWait = (ms) => IS_BUN_WINDOWS && ms < BUN_WINDOWS_MIN_WAIT_MS ? BUN_WINDOWS_MIN_WAIT_MS : ms;
|
|
51
|
+
// Windows process workers cannot be woken through the native address-based
|
|
52
|
+
// wait, so cap their wait to 1ms and let polling rediscover work.
|
|
55
53
|
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
|
+
const pollingWaitTimeoutMs = (parkMs) => floorBunWindowsWait(Number.isFinite(parkMs) ? Math.min(Math.max(parkMs, 0), 1) : 1);
|
|
57
55
|
export const whilePausing = ({ pauseInNanoseconds }) => {
|
|
58
56
|
const forNanoseconds = pauseInNanoseconds ?? DEFAULT_PAUSE_TIME;
|
|
59
57
|
if (!a_pause || forNanoseconds <= 0)
|
|
@@ -105,15 +103,12 @@ export const sleepUntilChanged = ({ at, opView, pauseInNanoseconds, rxStatus, tx
|
|
|
105
103
|
else if (useSharedMemoryWait &&
|
|
106
104
|
a_wait &&
|
|
107
105
|
opView.buffer instanceof SharedArrayBuffer) {
|
|
108
|
-
// This
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
a_wait(opView, at, value, parkMs ?? Infinity);
|
|
106
|
+
// This path can be woken with Atomics.notify; an infinite debug wait is
|
|
107
|
+
// intentional.
|
|
108
|
+
a_wait(opView, at, value, floorBunWindowsWait(parkMs ?? Infinity));
|
|
112
109
|
}
|
|
113
110
|
else if (a_wait && waitFallbackView) {
|
|
114
|
-
//
|
|
115
|
-
// re-polls. Keep the slice short and finite so FFI-backed process workers
|
|
116
|
-
// stay responsive without a cross-process native wake primitive.
|
|
111
|
+
// This fallback is timeout-driven; keep the slice short for responsiveness.
|
|
117
112
|
a_wait(waitFallbackView, 0, 0, pollingWaitTimeoutMs(parkMs));
|
|
118
113
|
}
|
|
119
114
|
a_store(rxStatus, 0, 1);
|
package/unsafe.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { BUFFER_REFERENCE_CODEC_ID, BUFFER_REFERENCE_KIND, BufferReference, type BufferReferenceMetadata,
|
|
1
|
+
export { BUFFER_REFERENCE_CODEC_ID, BUFFER_REFERENCE_KIND, BufferReference, type BufferReferenceMetadata, type BufferReferenceRuntime, isBufferReferenceMetadata, } from "./src/connections/buffer-reference.js";
|
|
2
|
+
export { sharedBytes } from "./src/worker/shared-return.js";
|
package/unsafe.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { BUFFER_REFERENCE_CODEC_ID, BUFFER_REFERENCE_KIND, BufferReference,
|
|
1
|
+
export { BUFFER_REFERENCE_CODEC_ID, BUFFER_REFERENCE_KIND, BufferReference, isBufferReferenceMetadata, } from "./src/connections/buffer-reference.js";
|
|
2
|
+
export { sharedBytes } from "./src/worker/shared-return.js";
|