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
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import { createRuntimeMessageChannel, } from "../common/worker-runtime.js";
|
|
2
|
-
|
|
2
|
+
import { RUNTIME, SET_IMMEDIATE } from "../common/runtime.js";
|
|
3
|
+
/**
|
|
4
|
+
* Macrotask primitive for the host pump, picked per runtime: a round trip costs
|
|
5
|
+
* 757ns on Bun via MessageChannel but 4662ns on Deno, where `setImmediate` is
|
|
6
|
+
* 1110ns. Bun keeps the channel; browser and Andromeda have no choice.
|
|
7
|
+
*
|
|
8
|
+
* `serial-channel` opts back into the channel explicitly (see `src/api.ts`): one
|
|
9
|
+
* hop drives every lane's check in turn and relies on the channel's delivery to
|
|
10
|
+
* do it, so a merely cheaper pump starves it -- it cost 25% throughput there.
|
|
11
|
+
*/
|
|
12
|
+
const IMMEDIATE_PUMP = RUNTIME === "deno" || RUNTIME === "node"
|
|
13
|
+
? SET_IMMEDIATE
|
|
14
|
+
: undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Free drain hops before `scheduleNotify` stops re-arming the pump for free.
|
|
17
|
+
*
|
|
18
|
+
* The window has to match what the dispatcher escalates *to*, so these are
|
|
19
|
+
* chosen together and `canUseDoorbell` picks between them. Polling escalates to
|
|
20
|
+
* the `setTimeout` ladder, whose finest rung is ~1.1ms on every runtime, so it
|
|
21
|
+
* wants a wide window; that sleep is also load-bearing, since it batches
|
|
22
|
+
* completions. A doorbell escalates to `Atomics.waitAsync` at about the price of
|
|
23
|
+
* one hop, so it wants a narrow one.
|
|
24
|
+
*
|
|
25
|
+
* Mixing them is the trap: a doorbell behind the wide window keeps every poll
|
|
26
|
+
* hop *and* adds the arm, and loses to plain polling at every thread count.
|
|
27
|
+
* Measurements behind both values: `docs/host-doorbell-proposal.md`.
|
|
28
|
+
*/
|
|
29
|
+
const POLL_STALL_FREE_LOOPS = 128;
|
|
30
|
+
const DOORBELL_STALL_FREE_LOOPS = 1;
|
|
31
|
+
export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, }, queue: { completeFrame, hasPendingFrames, flushToWorker, txIdle, waitForCompletion, setCompletionWaiterArmed, }, channelHandler, dispatcherOptions, notifySignal, crossProcess, }) => {
|
|
3
32
|
const a_load = Atomics.load;
|
|
4
33
|
const a_store = Atomics.store;
|
|
5
34
|
const a_notify = Atomics.notify;
|
|
@@ -10,13 +39,81 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
|
|
|
10
39
|
a_notify(opView, 0, 1);
|
|
11
40
|
});
|
|
12
41
|
const notify = () => channelHandler.notify();
|
|
42
|
+
// `crossProcess` is a capability, not a preference, so it overrides an
|
|
43
|
+
// explicit `doorbell: true`: V8's Atomics waiter list is per isolate, so a
|
|
44
|
+
// worker in another process can never ring the host's waiter, and an armed
|
|
45
|
+
// doorbell would just sleep to the watchdog.
|
|
46
|
+
const canUseDoorbell = crossProcess !== true &&
|
|
47
|
+
(dispatcherOptions?.doorbell ?? true) &&
|
|
48
|
+
(RUNTIME === "bun" || RUNTIME === "node") &&
|
|
49
|
+
typeof Atomics.waitAsync === "function";
|
|
50
|
+
let doorbellEnabled = canUseDoorbell;
|
|
51
|
+
let doorbellArmed = false;
|
|
52
|
+
let doorbellEpoch = 0 | 0;
|
|
53
|
+
const DOORBELL_WATCHDOG_MS = 1000;
|
|
13
54
|
let stallCount = 0 | 0;
|
|
14
|
-
const
|
|
55
|
+
const requestedStallFreeLoops = dispatcherOptions?.stallFreeLoops;
|
|
56
|
+
let stallFreeLoops = requestedStallFreeLoops !== undefined
|
|
57
|
+
? Math.max(0, requestedStallFreeLoops | 0)
|
|
58
|
+
: canUseDoorbell
|
|
59
|
+
? DOORBELL_STALL_FREE_LOOPS
|
|
60
|
+
: POLL_STALL_FREE_LOOPS;
|
|
15
61
|
const MAX_BACKOFF_MS = Math.max(0, (dispatcherOptions?.maxBackoffMs ?? 10) | 0);
|
|
16
62
|
let backoffTimer;
|
|
17
63
|
// inFlight prevents re-entrancy when pool.ts fires check() concurrently
|
|
18
64
|
// from both send() and the channel callback. Cheaper than try/finally.
|
|
19
65
|
let inFlight = false;
|
|
66
|
+
const cancelDoorbell = () => {
|
|
67
|
+
if (!doorbellArmed)
|
|
68
|
+
return;
|
|
69
|
+
doorbellEpoch = (doorbellEpoch + 1) | 0;
|
|
70
|
+
doorbellArmed = false;
|
|
71
|
+
setCompletionWaiterArmed(false);
|
|
72
|
+
};
|
|
73
|
+
const armDoorbell = () => {
|
|
74
|
+
if (!doorbellEnabled || doorbellArmed === true) {
|
|
75
|
+
if (!doorbellEnabled)
|
|
76
|
+
notify();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const token = (doorbellEpoch + 1) | 0;
|
|
80
|
+
doorbellEpoch = token;
|
|
81
|
+
doorbellArmed = true;
|
|
82
|
+
let woke = false;
|
|
83
|
+
const wake = () => {
|
|
84
|
+
if (!doorbellArmed || doorbellEpoch !== token || woke)
|
|
85
|
+
return;
|
|
86
|
+
woke = true;
|
|
87
|
+
doorbellArmed = false;
|
|
88
|
+
doorbellEpoch = (doorbellEpoch + 1) | 0;
|
|
89
|
+
setCompletionWaiterArmed(false);
|
|
90
|
+
// Keep the existing macrotask boundary. Calling check directly from a
|
|
91
|
+
// resolved waitAsync promise can chain microtasks under a hot workload
|
|
92
|
+
// and starve host I/O.
|
|
93
|
+
notify();
|
|
94
|
+
};
|
|
95
|
+
let supported = false;
|
|
96
|
+
try {
|
|
97
|
+
supported = waitForCompletion(wake, DOORBELL_WATCHDOG_MS);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
supported = false;
|
|
101
|
+
}
|
|
102
|
+
// Some runtimes reject waitAsync on their SharedArrayBuffer variants; fall
|
|
103
|
+
// back to polling rather than hanging the pool.
|
|
104
|
+
if (!supported) {
|
|
105
|
+
doorbellEnabled = false;
|
|
106
|
+
// The window was narrowed for a doorbell that no longer exists; widen it
|
|
107
|
+
// back or the ladder fires after a single fruitless drain.
|
|
108
|
+
if (requestedStallFreeLoops === undefined) {
|
|
109
|
+
stallFreeLoops = POLL_STALL_FREE_LOOPS;
|
|
110
|
+
}
|
|
111
|
+
doorbellArmed = false;
|
|
112
|
+
doorbellEpoch = (doorbellEpoch + 1) | 0;
|
|
113
|
+
setCompletionWaiterArmed(false);
|
|
114
|
+
notify();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
20
117
|
const check = () => {
|
|
21
118
|
if (inFlight) {
|
|
22
119
|
// Another check() is already mid-drain; mark that a re-run is needed
|
|
@@ -24,6 +121,16 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
|
|
|
24
121
|
check.rerun = true;
|
|
25
122
|
return;
|
|
26
123
|
}
|
|
124
|
+
// A waitAsync cannot be cancelled, so bumping its epoch makes the eventual
|
|
125
|
+
// callback inert instead.
|
|
126
|
+
cancelDoorbell();
|
|
127
|
+
// Idle lane: skip the drain. Otherwise it pays a txStatus write, an rxStatus
|
|
128
|
+
// load, and an Atomics.notify that wakes a parked worker for nothing — the
|
|
129
|
+
// waste that makes serial-channel latency grow with thread count.
|
|
130
|
+
if (txIdle()) {
|
|
131
|
+
check.isRunning = false;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
27
134
|
inFlight = true;
|
|
28
135
|
if (backoffTimer !== undefined) {
|
|
29
136
|
clearTimeout(backoffTimer);
|
|
@@ -37,25 +144,27 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
|
|
|
37
144
|
a_store(opView, 0, 1);
|
|
38
145
|
wakeSignal();
|
|
39
146
|
}
|
|
40
|
-
//
|
|
41
|
-
|
|
147
|
+
// Local vars so V8 keeps them as unboxed int32. Only a reaped completion
|
|
148
|
+
// counts as progress for `stallCount`: the pump exists to notice work a
|
|
149
|
+
// worker cannot announce, and letting a flush reset the counter would
|
|
150
|
+
// leave the escalation unreachable for a pool that never runs dry.
|
|
151
|
+
let completed = false;
|
|
42
152
|
let progressed = true;
|
|
43
153
|
while (progressed) {
|
|
44
154
|
progressed = false;
|
|
45
155
|
if (completeFrame() > 0) {
|
|
46
156
|
progressed = true;
|
|
47
|
-
|
|
157
|
+
completed = true;
|
|
48
158
|
}
|
|
49
159
|
while (hasPendingFrames()) {
|
|
50
160
|
if (!flushToWorker())
|
|
51
161
|
break;
|
|
52
162
|
progressed = true;
|
|
53
|
-
anyProgressed = true;
|
|
54
163
|
}
|
|
55
164
|
}
|
|
56
165
|
txStatus[0] = 0;
|
|
57
166
|
if (!txIdle()) {
|
|
58
|
-
if (
|
|
167
|
+
if (completed || hasPendingFrames()) {
|
|
59
168
|
stallCount = 0 | 0;
|
|
60
169
|
}
|
|
61
170
|
else {
|
|
@@ -74,14 +183,22 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
|
|
|
74
183
|
check.isRunning = false;
|
|
75
184
|
check.rerun = false;
|
|
76
185
|
const scheduleNotify = () => {
|
|
77
|
-
if (stallCount <=
|
|
186
|
+
if (stallCount <= stallFreeLoops) {
|
|
78
187
|
notify();
|
|
79
188
|
return;
|
|
80
189
|
}
|
|
190
|
+
if (doorbellEnabled) {
|
|
191
|
+
// Release isRunning during the wait so a fresh send() can restart the
|
|
192
|
+
// dispatcher immediately. The epoch in cancelDoorbell() invalidates the
|
|
193
|
+
// old waiter in that case.
|
|
194
|
+
check.isRunning = false;
|
|
195
|
+
armDoorbell();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
81
198
|
// One delayed wakeup at a time; fresh send() calls preempt via check directly.
|
|
82
199
|
if (backoffTimer !== undefined)
|
|
83
200
|
return;
|
|
84
|
-
let delay = (stallCount -
|
|
201
|
+
let delay = (stallCount - stallFreeLoops - 1) | 0;
|
|
85
202
|
if (delay < 0)
|
|
86
203
|
delay = 0;
|
|
87
204
|
else if (delay > MAX_BACKOFF_MS)
|
|
@@ -99,25 +216,47 @@ export const hostDispatcherLoop = ({ signalBox: { opView, txStatus, rxStatus, },
|
|
|
99
216
|
return { check };
|
|
100
217
|
};
|
|
101
218
|
export class ChannelHandler {
|
|
219
|
+
// Set only when the pump is a MessageChannel. The `setImmediate` pump has no
|
|
220
|
+
// ports, and nothing outside this class reads them.
|
|
102
221
|
channel;
|
|
103
222
|
port1;
|
|
104
223
|
port2;
|
|
105
|
-
#
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
224
|
+
#handler;
|
|
225
|
+
#notify;
|
|
226
|
+
constructor(pump = "auto") {
|
|
227
|
+
if (pump === "auto" && IMMEDIATE_PUMP !== undefined) {
|
|
228
|
+
const immediate = IMMEDIATE_PUMP;
|
|
229
|
+
// Allocated once; the pump fires hundreds of thousands of times a second.
|
|
230
|
+
// Routing via `#handler` also makes a callback outliving `close()` inert.
|
|
231
|
+
const run = () => {
|
|
232
|
+
this.#handler?.();
|
|
233
|
+
};
|
|
234
|
+
this.#notify = () => {
|
|
235
|
+
immediate(run);
|
|
236
|
+
};
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const channel = createRuntimeMessageChannel();
|
|
240
|
+
const port2 = channel.port2;
|
|
241
|
+
this.channel = channel;
|
|
242
|
+
this.port1 = channel.port1;
|
|
243
|
+
this.port2 = port2;
|
|
244
|
+
this.#notify = () => {
|
|
245
|
+
port2.postMessage(null);
|
|
246
|
+
};
|
|
111
247
|
}
|
|
112
248
|
notify() {
|
|
113
|
-
this.#
|
|
249
|
+
this.#notify();
|
|
114
250
|
}
|
|
115
251
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
252
|
+
* Registers the handler the pump calls back into. On the channel pump this is
|
|
253
|
+
* also where the ports are opened, so `notify` can reach port 1.
|
|
118
254
|
*/
|
|
119
255
|
open(f) {
|
|
256
|
+
this.#handler = f;
|
|
120
257
|
const port1 = this.port1;
|
|
258
|
+
if (port1 === undefined)
|
|
259
|
+
return;
|
|
121
260
|
if (typeof port1.on === "function") {
|
|
122
261
|
port1.on("message", f);
|
|
123
262
|
}
|
|
@@ -125,13 +264,16 @@ export class ChannelHandler {
|
|
|
125
264
|
// @ts-ignore
|
|
126
265
|
port1.onmessage = f;
|
|
127
266
|
}
|
|
128
|
-
this.port1
|
|
129
|
-
this.port2
|
|
267
|
+
this.port1?.start?.();
|
|
268
|
+
this.port2?.start?.();
|
|
130
269
|
}
|
|
131
270
|
/**
|
|
132
|
-
*
|
|
271
|
+
* Detaches the handler, and closes the channel if this pump has one.
|
|
133
272
|
*/
|
|
134
273
|
close() {
|
|
274
|
+
this.#handler = undefined;
|
|
275
|
+
if (this.port1 === undefined || this.port2 === undefined)
|
|
276
|
+
return;
|
|
135
277
|
//@ts-ignore
|
|
136
278
|
this.port1.onmessage = null;
|
|
137
279
|
//@ts-ignore
|
|
@@ -81,9 +81,14 @@ const composeInlineCallable = (fn, timeout, useAbortToolkit = false) => {
|
|
|
81
81
|
};
|
|
82
82
|
};
|
|
83
83
|
export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
|
|
84
|
+
// `createPool` passes an already name-sorted array, which is the order
|
|
85
|
+
// `fnNumber` indexes into. The object form has to reach the same order on its
|
|
86
|
+
// own: task ids identify a task, they do not rank it, so sorting by id would
|
|
87
|
+
// hand callers a different lane than the one they addressed.
|
|
84
88
|
const entries = Array.isArray(tasks)
|
|
85
89
|
? tasks
|
|
86
|
-
: Object.values(tasks)
|
|
90
|
+
: Object.values(tasks)
|
|
91
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
87
92
|
const runners = entries.map((entry) => {
|
|
88
93
|
// Imported tasks must never execute on the host inline lane: their module
|
|
89
94
|
// import is meant to stay inside the worker so worker permission policies
|
package/src/runtime/pool.d.ts
CHANGED
|
@@ -1,12 +1,115 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHostTxQueue } from "./tx-queue.js";
|
|
2
|
+
import { type ProcessSharedMemoryBacking, type ProcessStealMemoryLayout, type ResolvedProcessSharedMemorySettings } from "./process-worker.js";
|
|
2
3
|
import { type Sab } from "../ipc/transport/shared-memory.js";
|
|
3
4
|
import { ChannelHandler, type DispatcherCheck } from "./dispatcher.js";
|
|
4
|
-
import { lock2 } from "../memory/lock.js";
|
|
5
|
+
import { lock2, type Task } from "../memory/lock.js";
|
|
5
6
|
import "../memory/payloadCodec.js";
|
|
6
|
-
import type { DebugOptions, DispatcherSettings, WorkerContext, WorkerData, WorkerSettings } from "../types.js";
|
|
7
|
+
import type { DebugOptions, DispatcherSettings, LockBuffers, WorkerContext, WorkerData, WorkerSettings } from "../types.js";
|
|
7
8
|
import "../worker/loop.js";
|
|
8
9
|
import { type PayloadBufferOptions } from "../memory/payload-config.js";
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Build the buffers, host-side locks and pending registry that a stealing pool
|
|
12
|
+
* shares. One submit region for every worker to claim from, one private return
|
|
13
|
+
* region per worker, and a single pool-global queue so a response arriving on
|
|
14
|
+
* any lane settles the right promise.
|
|
15
|
+
*
|
|
16
|
+
* Region width follows paper §6.1: the largest power-of-two `g` leaving a spare
|
|
17
|
+
* region for a delayed claimant (`slots / g >= consumers + 1`). At 32 slots that
|
|
18
|
+
* caps how wide a region can be well before it caps the worker count.
|
|
19
|
+
*/
|
|
20
|
+
export declare const resolveStealRegionLanes: (consumers: number) => number;
|
|
21
|
+
/** Maximum claimants that leave the protocol's required spare region. */
|
|
22
|
+
export declare const MAX_STEAL_CONSUMERS: number;
|
|
23
|
+
export declare const createStealPoolBuffers: ({ threads, payload, regionLanes, abortSignalCapacity, usesAbortSignal, processWorker, }: {
|
|
24
|
+
threads: number;
|
|
25
|
+
payload?: PayloadBufferOptions;
|
|
26
|
+
regionLanes?: number;
|
|
27
|
+
abortSignalCapacity?: number;
|
|
28
|
+
usesAbortSignal?: boolean;
|
|
29
|
+
processWorker?: {
|
|
30
|
+
signalBytes: number;
|
|
31
|
+
sharedMemory: ResolvedProcessSharedMemorySettings;
|
|
32
|
+
};
|
|
33
|
+
}) => {
|
|
34
|
+
submitBuffers: LockBuffers;
|
|
35
|
+
returnBuffers: LockBuffers[];
|
|
36
|
+
hostSubmitLock: {
|
|
37
|
+
enlist: (task: Task) => true;
|
|
38
|
+
encode: (task: Task, state?: number) => boolean;
|
|
39
|
+
encodeManyFrom: (list: import("../ipc/tools/ring-queue.js").default<Task>) => number;
|
|
40
|
+
encodeAll: () => boolean;
|
|
41
|
+
publish: (task: Task) => boolean;
|
|
42
|
+
flushPending: () => boolean;
|
|
43
|
+
decode: () => boolean;
|
|
44
|
+
hasSpace: () => boolean;
|
|
45
|
+
resolved: import("../ipc/tools/ring-queue.js").default<Task>;
|
|
46
|
+
hostBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
|
|
47
|
+
workerBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
|
|
48
|
+
recyclecList: import("../ipc/tools/ring-queue.js").default<Task>;
|
|
49
|
+
resolveHost: ({ queue, onResolved, shouldSettle, activeRejectPlaceholder, }: {
|
|
50
|
+
queue: Task[];
|
|
51
|
+
onResolved?: (task: Task) => void;
|
|
52
|
+
shouldSettle?: (task: Task) => boolean;
|
|
53
|
+
activeRejectPlaceholder?: Task["reject"];
|
|
54
|
+
}) => () => number;
|
|
55
|
+
waitForHostChange: (timeoutMs?: number) => import("../memory/lock.js").WaitAsyncResult | undefined;
|
|
56
|
+
setHostWaiterArmed: (armed: boolean) => void;
|
|
57
|
+
hasPendingFrames: () => boolean;
|
|
58
|
+
getPendingFrameCount: () => number;
|
|
59
|
+
getPendingPromiseCount: () => number;
|
|
60
|
+
resetPendingState: () => void;
|
|
61
|
+
deactivateStealConsumer: (id: number) => boolean;
|
|
62
|
+
takeDeferredCount: () => number;
|
|
63
|
+
setPromiseHandler: (handler?: import("../memory/lock.js").PromisePayloadHandler) => void;
|
|
64
|
+
};
|
|
65
|
+
hostReturnLocks: {
|
|
66
|
+
enlist: (task: Task) => true;
|
|
67
|
+
encode: (task: Task, state?: number) => boolean;
|
|
68
|
+
encodeManyFrom: (list: import("../ipc/tools/ring-queue.js").default<Task>) => number;
|
|
69
|
+
encodeAll: () => boolean;
|
|
70
|
+
publish: (task: Task) => boolean;
|
|
71
|
+
flushPending: () => boolean;
|
|
72
|
+
decode: () => boolean;
|
|
73
|
+
hasSpace: () => boolean;
|
|
74
|
+
resolved: import("../ipc/tools/ring-queue.js").default<Task>;
|
|
75
|
+
hostBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
|
|
76
|
+
workerBits: Int32Array<import("../common/shared-buffer-region.js").SharedBuffer>;
|
|
77
|
+
recyclecList: import("../ipc/tools/ring-queue.js").default<Task>;
|
|
78
|
+
resolveHost: ({ queue, onResolved, shouldSettle, activeRejectPlaceholder, }: {
|
|
79
|
+
queue: Task[];
|
|
80
|
+
onResolved?: (task: Task) => void;
|
|
81
|
+
shouldSettle?: (task: Task) => boolean;
|
|
82
|
+
activeRejectPlaceholder?: Task["reject"];
|
|
83
|
+
}) => () => number;
|
|
84
|
+
waitForHostChange: (timeoutMs?: number) => import("../memory/lock.js").WaitAsyncResult | undefined;
|
|
85
|
+
setHostWaiterArmed: (armed: boolean) => void;
|
|
86
|
+
hasPendingFrames: () => boolean;
|
|
87
|
+
getPendingFrameCount: () => number;
|
|
88
|
+
getPendingPromiseCount: () => number;
|
|
89
|
+
resetPendingState: () => void;
|
|
90
|
+
deactivateStealConsumer: (id: number) => boolean;
|
|
91
|
+
takeDeferredCount: () => number;
|
|
92
|
+
setPromiseHandler: (handler?: import("../memory/lock.js").PromisePayloadHandler) => void;
|
|
93
|
+
}[];
|
|
94
|
+
sharedQueue: {
|
|
95
|
+
rejectAll: (reason: string) => void;
|
|
96
|
+
hasPendingFrames: () => boolean;
|
|
97
|
+
txIdle: () => boolean;
|
|
98
|
+
completeFrame: () => number;
|
|
99
|
+
waitForCompletion: (onWake: () => void, timeoutMs?: number) => boolean;
|
|
100
|
+
setCompletionWaiterArmed: (armed: boolean) => void;
|
|
101
|
+
setReturnHooks: (lane: number, hooks: (import("../connections/buffer-reference.js").BufferReferenceReturnHooks | ((token: bigint) => void)) | undefined) => void;
|
|
102
|
+
enqueue: (functionID: number, timeout?: import("../types.js").TaskTimeout, abortSignal?: import("../types.js").AbortSignalOption) => (rawArgs: unknown) => Promise<never> | import("../common/with-resolvers.js").PromiseWithMaybeReject<unknown>;
|
|
103
|
+
flushToWorker: () => boolean;
|
|
104
|
+
enqueueKnown: (task: Task) => boolean;
|
|
105
|
+
settlePromisePayload: (task: Task, isRejected: boolean, value: unknown) => boolean;
|
|
106
|
+
};
|
|
107
|
+
regionLanes: number;
|
|
108
|
+
processMemory: ProcessStealMemoryLayout;
|
|
109
|
+
abortSignalSAB: SharedArrayBuffer | import("../types.js").SharedBufferRegion;
|
|
110
|
+
abortSignalMax: number;
|
|
111
|
+
};
|
|
112
|
+
export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug, hostDebug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, abortSignalCapacity, usesAbortSignal, sharedChannelHandler, stealPool, }: {
|
|
10
113
|
list: string[];
|
|
11
114
|
ids: number[];
|
|
12
115
|
names: string[];
|
|
@@ -30,6 +133,26 @@ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug
|
|
|
30
133
|
* macro channel that runs all lane checks.
|
|
31
134
|
*/
|
|
32
135
|
sharedChannelHandler?: ChannelHandler;
|
|
136
|
+
/**
|
|
137
|
+
* Work-stealing wiring. When present the submit region, both host-side locks
|
|
138
|
+
* and the pending registry are owned by `createPool` and shared: every worker
|
|
139
|
+
* claims from one request region, and the host demultiplexes responses by
|
|
140
|
+
* task id. This lane only contributes its private return region and its
|
|
141
|
+
* worker. The dispatcher is owned by the pool too, so none is built here.
|
|
142
|
+
*/
|
|
143
|
+
stealPool?: {
|
|
144
|
+
submitBuffers: LockBuffers;
|
|
145
|
+
returnBuffers: LockBuffers;
|
|
146
|
+
hostSubmitLock: ReturnType<typeof lock2>;
|
|
147
|
+
hostReturnLock: ReturnType<typeof lock2>;
|
|
148
|
+
sharedQueue: ReturnType<typeof createHostTxQueue>;
|
|
149
|
+
consumers: number;
|
|
150
|
+
consumerId: number;
|
|
151
|
+
regionLanes: number;
|
|
152
|
+
abortSignalSAB?: LockBuffers["headers"];
|
|
153
|
+
abortSignalMax?: number;
|
|
154
|
+
processMemory?: ProcessStealMemoryLayout;
|
|
155
|
+
};
|
|
33
156
|
}) => (WorkerContext & {
|
|
34
157
|
dispatcherCheck?: never;
|
|
35
158
|
laneWake?: never;
|