@atolljs/core 0.1.6 → 0.1.7
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/dist/contract/sharedMemory.d.ts +2 -0
- package/dist/contract/types.d.ts +2 -0
- package/dist/devtools.d.ts +223 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17 -16
- package/dist/index10.js +14 -185
- package/dist/index11.js +310 -47
- package/dist/index12.js +46 -59
- package/dist/index13.js +59 -20
- package/dist/index14.js +24 -60
- package/dist/index15.js +56 -43
- package/dist/index16.js +44 -50
- package/dist/index17.js +52 -38
- package/dist/index18.js +36 -83
- package/dist/index19.js +81 -47
- package/dist/index20.js +48 -51
- package/dist/index21.js +57 -0
- package/dist/index5.js +156 -35
- package/dist/index6.js +32 -485
- package/dist/index7.js +508 -19
- package/dist/index8.js +19 -33
- package/dist/index9.js +29 -14
- package/dist/pool/workerClient.d.ts +2 -0
- package/dist/pool/workerPool.d.ts +4 -0
- package/package.json +1 -1
- package/src/contract/sharedMemory.ts +19 -3
- package/src/contract/types.ts +2 -0
- package/src/devtools.ts +490 -0
- package/src/index.ts +14 -0
- package/src/pool/workerClient.ts +3 -0
- package/src/pool/workerPool.ts +56 -7
- package/src/worker/workerBootstrap.ts +3 -0
|
@@ -49,6 +49,8 @@ export interface ConnectorContext {
|
|
|
49
49
|
view: Int32Array;
|
|
50
50
|
index: number;
|
|
51
51
|
};
|
|
52
|
+
/** The field's contract path — for devtools write events on granular commits. */
|
|
53
|
+
path?: string;
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
56
|
* A connector factory builds a connector for one field kind over a region of
|
package/dist/contract/types.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface WorkerPoolConfig<S extends SharedSpec = SharedSpec, T extends T
|
|
|
60
60
|
persistence?: (memory: SharedMemory<S>) => MemoryPersistence | undefined;
|
|
61
61
|
/** Worker count — a number, or 'auto' (the default) for navigator.hardwareConcurrency ?? 4. */
|
|
62
62
|
poolSize?: number | 'auto';
|
|
63
|
+
/** Display label for devtools/dashboards — falls back to a generated id. */
|
|
64
|
+
name?: string;
|
|
63
65
|
memory?: MemoryConfig;
|
|
64
66
|
/** Max in-flight tasks per worker (default 1); excess tasks queue FIFO. */
|
|
65
67
|
concurrency?: number;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured instrumentation events for the devtools/dashboard tooling.
|
|
3
|
+
* Same zero-cost contract as `log.ts` and islands' `proxyMetrics`: when no
|
|
4
|
+
* sink is installed, emit() is a single branch — production pays nothing.
|
|
5
|
+
*
|
|
6
|
+
* Events are emitted on whichever thread they occur on. Worker-side events
|
|
7
|
+
* reach the main thread's sink by postMessage forwarding (`ATOLL_DEVTOOLS`
|
|
8
|
+
* on the task channel — the pool re-emits them), so one sink sees the whole
|
|
9
|
+
* app. See docs/plans/devtools-dashboard.md.
|
|
10
|
+
*/
|
|
11
|
+
export type DevtoolsThread = 'main' | 'worker';
|
|
12
|
+
export type TaskSettleOutcome = 'ok' | 'error' | 'aborted' | 'timeout' | 'queue-full' | 'crashed';
|
|
13
|
+
export type DevtoolsEvent = {
|
|
14
|
+
type: 'pool:init';
|
|
15
|
+
poolId: string;
|
|
16
|
+
/** Config `name`, when supplied — a display label, not the id. */
|
|
17
|
+
label?: string;
|
|
18
|
+
poolSize: number;
|
|
19
|
+
concurrency: number;
|
|
20
|
+
memoryBytes?: number;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'pool:terminate';
|
|
23
|
+
poolId: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'worker:spawn';
|
|
26
|
+
poolId: string;
|
|
27
|
+
slot: number;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'worker:error';
|
|
30
|
+
poolId: string;
|
|
31
|
+
slot: number;
|
|
32
|
+
message: string;
|
|
33
|
+
} | {
|
|
34
|
+
type: 'worker:respawn';
|
|
35
|
+
poolId: string;
|
|
36
|
+
slot: number;
|
|
37
|
+
} | {
|
|
38
|
+
type: 'task:enqueue';
|
|
39
|
+
poolId: string;
|
|
40
|
+
callId: number;
|
|
41
|
+
taskId: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: 'task:dispatch';
|
|
44
|
+
poolId: string;
|
|
45
|
+
callId: number;
|
|
46
|
+
taskId: string;
|
|
47
|
+
slot: number;
|
|
48
|
+
waitMs: number;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'task:settle';
|
|
51
|
+
poolId: string;
|
|
52
|
+
callId: number;
|
|
53
|
+
taskId: string;
|
|
54
|
+
outcome: TaskSettleOutcome;
|
|
55
|
+
/** dispatch→reply; absent for calls that never ran. */
|
|
56
|
+
runMs?: number;
|
|
57
|
+
error?: string;
|
|
58
|
+
} | {
|
|
59
|
+
type: 'memory:bind';
|
|
60
|
+
/** Version-counter index → field path, for pairing memory:write. */
|
|
61
|
+
fields: [index: number, path: string][];
|
|
62
|
+
totalBytes: number;
|
|
63
|
+
} | {
|
|
64
|
+
type: 'memory:write';
|
|
65
|
+
path: string;
|
|
66
|
+
version: number;
|
|
67
|
+
} | {
|
|
68
|
+
type: 'island:mount';
|
|
69
|
+
instance: string;
|
|
70
|
+
app: string;
|
|
71
|
+
pid: string;
|
|
72
|
+
/** The client pool driving this island — links the island to its worker. */
|
|
73
|
+
poolId?: string;
|
|
74
|
+
/** Renderer/framework tag supplied by the mounter — observability only. */
|
|
75
|
+
framework?: string;
|
|
76
|
+
} | {
|
|
77
|
+
type: 'island:unmount';
|
|
78
|
+
instance: string;
|
|
79
|
+
} | {
|
|
80
|
+
type: 'island:event';
|
|
81
|
+
instance: string;
|
|
82
|
+
name: string;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'island:task';
|
|
85
|
+
instance: string;
|
|
86
|
+
/** The client call behind the round-trip — the island's "request". */
|
|
87
|
+
method: 'mount' | 'whoami' | 'dispatch' | 'setSize' | 'updateProps' | 'flush' | 'unmount';
|
|
88
|
+
/** Round-trip ms: call → ops returned (worker render + transport). */
|
|
89
|
+
ms: number;
|
|
90
|
+
/** Op count in the returned batch (response size in ops). */
|
|
91
|
+
ops?: number;
|
|
92
|
+
error?: string;
|
|
93
|
+
} | {
|
|
94
|
+
type: 'island:ops';
|
|
95
|
+
instance: string;
|
|
96
|
+
/** The client call that produced the batch. */
|
|
97
|
+
via: 'mount' | 'updateProps' | 'dispatch' | 'setSize' | 'flush' | 'unmount';
|
|
98
|
+
count: number;
|
|
99
|
+
/** Main-thread replay time; worker-side render time rides call timings. */
|
|
100
|
+
replayMs?: number;
|
|
101
|
+
} | {
|
|
102
|
+
type: 'net:fetch';
|
|
103
|
+
/** Per-context sequence — correlates the net:fetch-detail follow-up. */
|
|
104
|
+
id: number;
|
|
105
|
+
url: string;
|
|
106
|
+
method: string;
|
|
107
|
+
/** HTTP status — absent when the request failed before a response. */
|
|
108
|
+
status?: number;
|
|
109
|
+
/** Time from dispatch to response headers (error: to failure). */
|
|
110
|
+
ms: number;
|
|
111
|
+
/** Response Content-Length when advertised. */
|
|
112
|
+
bytes?: number;
|
|
113
|
+
error?: string;
|
|
114
|
+
reqHeaders?: [string, string][];
|
|
115
|
+
/** Capped preview; non-text bodies are described, not dumped. */
|
|
116
|
+
reqBody?: string;
|
|
117
|
+
} | {
|
|
118
|
+
/**
|
|
119
|
+
* Detail follow-up to net:fetch (same id) — emitted after the response
|
|
120
|
+
* settles so it can carry headers, a capped body preview, and the
|
|
121
|
+
* Resource Timing stage breakdown once the entry exists.
|
|
122
|
+
*/
|
|
123
|
+
type: 'net:fetch-detail';
|
|
124
|
+
id: number;
|
|
125
|
+
/** Present when the body was only readable async (Request objects). */
|
|
126
|
+
reqBody?: string;
|
|
127
|
+
resHeaders?: [string, string][];
|
|
128
|
+
resBody?: string;
|
|
129
|
+
/**
|
|
130
|
+
* Stage ms from PerformanceResourceTiming: queue (stalled → fetch
|
|
131
|
+
* start), dns, tcp, tls, wait (request → first byte), download.
|
|
132
|
+
* Absent for cross-origin responses without Timing-Allow-Origin.
|
|
133
|
+
*/
|
|
134
|
+
timing?: {
|
|
135
|
+
queue: number;
|
|
136
|
+
dns: number;
|
|
137
|
+
tcp: number;
|
|
138
|
+
tls: number;
|
|
139
|
+
wait: number;
|
|
140
|
+
download: number;
|
|
141
|
+
};
|
|
142
|
+
transferSize?: number;
|
|
143
|
+
encodedSize?: number;
|
|
144
|
+
decodedSize?: number;
|
|
145
|
+
} | {
|
|
146
|
+
type: 'runtime:memory';
|
|
147
|
+
/**
|
|
148
|
+
* measureUserAgentSpecificMemory: cluster total.
|
|
149
|
+
* performance.memory / process.memoryUsage fallback: this context's
|
|
150
|
+
* usedJSHeapSize / heapUsed.
|
|
151
|
+
*/
|
|
152
|
+
heapBytes: number;
|
|
153
|
+
heapLimitBytes?: number;
|
|
154
|
+
/** Resident set size — present on the Node memoryUsage() path. */
|
|
155
|
+
rssBytes?: number;
|
|
156
|
+
/**
|
|
157
|
+
* Per-execution-context attribution from
|
|
158
|
+
* `performance.measureUserAgentSpecificMemory` — emitted on the main
|
|
159
|
+
* thread only (workers can't self-identify among same-URL contexts).
|
|
160
|
+
* Worker contexts are identified by their script URL.
|
|
161
|
+
*/
|
|
162
|
+
contexts?: {
|
|
163
|
+
bytes: number;
|
|
164
|
+
scope?: string;
|
|
165
|
+
url?: string;
|
|
166
|
+
}[];
|
|
167
|
+
};
|
|
168
|
+
export type EmittedDevtoolsEvent = DevtoolsEvent & {
|
|
169
|
+
/** performance.now() on the emitting thread. */
|
|
170
|
+
at: number;
|
|
171
|
+
/** Which side of the boundary emitted the event. */
|
|
172
|
+
thread: DevtoolsThread;
|
|
173
|
+
/**
|
|
174
|
+
* Stamped by the pool when re-emitting a worker-forwarded event —
|
|
175
|
+
* attributes worker-side events (memory writes, fetches, heap samples)
|
|
176
|
+
* to the specific pool slot that produced them.
|
|
177
|
+
*/
|
|
178
|
+
worker?: {
|
|
179
|
+
poolId: string;
|
|
180
|
+
slot: number;
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
export type DevtoolsSink = (event: EmittedDevtoolsEvent) => void;
|
|
184
|
+
/** Install the event sink (devtools transports hook here); null disables. */
|
|
185
|
+
export declare const setDevtoolsSink: (custom: DevtoolsSink | null) => void;
|
|
186
|
+
/** Emit a structured event — one branch when no sink is installed. */
|
|
187
|
+
export declare const emitDevtools: (event: DevtoolsEvent) => void;
|
|
188
|
+
/**
|
|
189
|
+
* Re-emit an already-stamped event — for forwarding worker-side events that
|
|
190
|
+
* arrived over postMessage without re-stamping their timing/thread.
|
|
191
|
+
*/
|
|
192
|
+
export declare const forwardDevtools: (event: EmittedDevtoolsEvent) => void;
|
|
193
|
+
/** True when a devtools sink is installed on this thread. */
|
|
194
|
+
export declare const devtoolsEnabled: () => boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Worker side: route emitted events to the main thread over the task
|
|
197
|
+
* channel (`ATOLL_DEVTOOLS` messages — the pool re-emits them into its
|
|
198
|
+
* sink). Switched on by the pool's INIT handshake when the pool has a
|
|
199
|
+
* sink installed, so workers only forward when someone is listening.
|
|
200
|
+
*/
|
|
201
|
+
export declare const enableWorkerDevtoolsForwarding: () => void;
|
|
202
|
+
/**
|
|
203
|
+
* Wrap `scope.fetch` to emit `net:fetch` (+ a `net:fetch-detail` follow-up
|
|
204
|
+
* with headers/body previews and stage timings) for every request — covers
|
|
205
|
+
* main thread (installed by connectDevtools) and workers (installed by the
|
|
206
|
+
* INIT handshake's forwarding flag). Idempotent; restores nothing (the
|
|
207
|
+
* wrapper delegates to the captured original). XHR is not covered.
|
|
208
|
+
*/
|
|
209
|
+
export declare const installFetchProbe: (scope: {
|
|
210
|
+
fetch?: typeof fetch;
|
|
211
|
+
}) => void;
|
|
212
|
+
/**
|
|
213
|
+
* Emit `runtime:memory` periodically — JS heap usage. On the main thread,
|
|
214
|
+
* prefers `performance.measureUserAgentSpecificMemory()`, which reports a
|
|
215
|
+
* per-execution-context breakdown (windows + workers identified by script
|
|
216
|
+
* URL) for the whole agent cluster; falls back to `performance.memory`,
|
|
217
|
+
* which also covers the worker-side install on engines that expose it
|
|
218
|
+
* there, then `process.memoryUsage()` on Node (heapUsed + rss — works
|
|
219
|
+
* inside worker_threads too). No-ops with none of the three.
|
|
220
|
+
*/
|
|
221
|
+
export declare const installMemoryProbe: (scope: object, intervalMs?: number) => void;
|
|
222
|
+
/** Process-unique ids for pools — 'pool-1', 'pool-2', … */
|
|
223
|
+
export declare const nextDevtoolsId: (prefix: string) => string;
|
package/dist/index.d.ts
CHANGED
|
@@ -29,3 +29,5 @@ export { defineTask, isAsyncTask, toTask } from './task.js';
|
|
|
29
29
|
export type { AsyncTask, TaskSnapshot } from './task.js';
|
|
30
30
|
export { getLogLevel, log, scoped, setLogLevel, setLogSink } from './log.js';
|
|
31
31
|
export type { LogEntry, LogLevel, LogSink } from './log.js';
|
|
32
|
+
export { devtoolsEnabled, emitDevtools, installFetchProbe, installMemoryProbe, setDevtoolsSink, } from './devtools.js';
|
|
33
|
+
export type { DevtoolsEvent, DevtoolsSink, DevtoolsThread, EmittedDevtoolsEvent, TaskSettleOutcome, } from './devtools.js';
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { msgpackrCodec as e } from "./index2.js";
|
|
2
2
|
import { SchemaError as t, z as n } from "./index3.js";
|
|
3
3
|
import { listSchema as r } from "./index4.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
4
|
+
import { devtoolsEnabled as i, emitDevtools as a, installFetchProbe as o, installMemoryProbe as s, setDevtoolsSink as c } from "./index5.js";
|
|
5
|
+
import { getLogLevel as l, log as u, scoped as d, setLogLevel as f, setLogSink as p } from "./index6.js";
|
|
6
|
+
import { bindSharedMemories as m, defineSharedMemory as h, field as g, getDefinedSharedMemoryCount as _, jsonCodec as v, registerConnectorFactory as y } from "./index7.js";
|
|
7
|
+
import { reef as b } from "./index8.js";
|
|
8
|
+
import { MemoryManager as x } from "./index9.js";
|
|
9
|
+
import { PoolQueueFullError as S, TaskAbortedError as C, TaskTimeoutError as w, WorkerCrashedError as T } from "./index10.js";
|
|
10
|
+
import { WorkerPool as E } from "./index11.js";
|
|
11
|
+
import { connectWorker as D, workerClient as O } from "./index12.js";
|
|
12
|
+
import { connectSharedWorker as k } from "./index13.js";
|
|
13
|
+
import { TaskRegistry as A } from "./index14.js";
|
|
14
|
+
import { sharedWorkerHost as j } from "./index15.js";
|
|
15
|
+
import { createClient as M, defineService as N, implementService as P, rpc as F, serviceMethod as I } from "./index16.js";
|
|
16
|
+
import { RESERVED_CLIENT_KEYS as L, defineWorker as R } from "./index18.js";
|
|
17
|
+
import { reactive as z, shallowEqual as B, watch as V } from "./index19.js";
|
|
18
|
+
import { observe as H } from "./index20.js";
|
|
19
|
+
import { defineTask as U, isAsyncTask as W, toTask as G } from "./index21.js";
|
|
20
|
+
export { x as MemoryManager, S as PoolQueueFullError, L as RESERVED_CLIENT_KEYS, t as SchemaError, C as TaskAbortedError, A as TaskRegistry, w as TaskTimeoutError, T as WorkerCrashedError, E as WorkerPool, m as bindSharedMemories, k as connectSharedWorker, D as connectWorker, M as createClient, N as defineService, h as defineSharedMemory, U as defineTask, R as defineWorker, i as devtoolsEnabled, a as emitDevtools, g as field, _ as getDefinedSharedMemoryCount, l as getLogLevel, P as implementService, o as installFetchProbe, s as installMemoryProbe, W as isAsyncTask, v as jsonCodec, r as listSchema, u as log, e as msgpackrCodec, H as observe, z as reactive, b as reef, y as registerConnectorFactory, F as rpc, d as scoped, I as serviceMethod, c as setDevtoolsSink, f as setLogLevel, p as setLogSink, B as shallowEqual, j as sharedWorkerHost, G as toTask, V as watch, O as workerClient, n as z };
|
package/dist/index10.js
CHANGED
|
@@ -1,191 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var s = t("pool"), c = t("task"), l = () => ({
|
|
6
|
-
count: 0,
|
|
7
|
-
mean: 0,
|
|
8
|
-
max: 0,
|
|
9
|
-
add(e) {
|
|
10
|
-
this.count++, this.mean += (e - this.mean) / this.count, e > this.max && (this.max = e);
|
|
1
|
+
//#region src/pool/errors.ts
|
|
2
|
+
var e = class extends Error {
|
|
3
|
+
constructor(e = "WorkerPool task queue is full") {
|
|
4
|
+
super(e), this.name = "PoolQueueFullError";
|
|
11
5
|
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
nextMessageId = 0;
|
|
16
|
-
queue = [];
|
|
17
|
-
memoryManager;
|
|
18
|
-
workerUrl;
|
|
19
|
-
spawn;
|
|
20
|
-
concurrency;
|
|
21
|
-
maxQueue;
|
|
22
|
-
respawn;
|
|
23
|
-
taskTimeout;
|
|
24
|
-
closed = !1;
|
|
25
|
-
drainWaiters = [];
|
|
26
|
-
statsAgg = {
|
|
27
|
-
completed: 0,
|
|
28
|
-
failed: 0,
|
|
29
|
-
aborted: 0,
|
|
30
|
-
waitMs: l(),
|
|
31
|
-
runMs: l()
|
|
32
|
-
};
|
|
33
|
-
sharedMemory;
|
|
34
|
-
persistence;
|
|
35
|
-
constructor(t) {
|
|
36
|
-
if (t.sharedMemory && (typeof SharedArrayBuffer > "u" || typeof crossOriginIsolated < "u" && !crossOriginIsolated)) throw Error("WorkerPool requires SharedArrayBuffer in a cross-origin isolated context — serve the page with COOP/COEP headers.");
|
|
37
|
-
if (!t.workerUrl && !t.createWorker) throw Error("WorkerPool requires either `workerUrl` or `createWorker` in its config.");
|
|
38
|
-
this.workerUrl = t.workerUrl, this.spawn = t.createWorker ?? (() => new Worker(this.workerUrl, { type: "module" })), this.concurrency = t.concurrency ?? 1, this.maxQueue = t.maxQueue ?? Infinity, this.respawn = t.respawn ?? !0, this.taskTimeout = t.taskTimeout, t.sharedMemory && (this.sharedMemory = t.sharedMemory, this.memoryManager = new n(t.memory), this.memoryManager.ensureCapacity(this.sharedMemory.totalBytes), this.sharedMemory.bind(this.memoryManager.getBuffer()), this.persistence = t.persistence?.(t.sharedMemory));
|
|
39
|
-
let r = t.poolSize ?? "auto", i = r === "auto" ? navigator.hardwareConcurrency ?? 4 : r;
|
|
40
|
-
s.info(this.sharedMemory ? `spawning ${i} worker(s) with ${e(this.sharedMemory.totalBytes)} shared buffer` : `spawning ${i} worker(s), message-only`, { workerUrl: t.workerUrl ? String(t.workerUrl) : "createWorker()" }), s.debug("tip: setLogLevel('trace') for per-task detail");
|
|
41
|
-
for (let e = 0; e < i; e++) this.addWorker(this.spawn());
|
|
42
|
-
let a = t.tasks ?? {};
|
|
43
|
-
for (let [e, t] of Object.entries(a)) {
|
|
44
|
-
if (e in this) {
|
|
45
|
-
s.warn(`task "${e}" collides with a WorkerPool member — not installed`);
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
this[e] = (...e) => this.runTask(t, ...e);
|
|
49
|
-
}
|
|
50
|
-
Object.keys(a).length > 0 && s.info(`task methods: ${Object.keys(a).join(", ")}`);
|
|
6
|
+
}, t = class extends Error {
|
|
7
|
+
constructor(e = "task aborted") {
|
|
8
|
+
super(e), this.name = "AbortError";
|
|
51
9
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
inFlight: 0
|
|
56
|
-
};
|
|
57
|
-
return e.addEventListener("message", (e) => {
|
|
58
|
-
this.handleReply(t, e.data);
|
|
59
|
-
}), e.addEventListener("error", (e) => {
|
|
60
|
-
this.handleWorkerError(t, e);
|
|
61
|
-
}), this.memoryManager ? e.postMessage({
|
|
62
|
-
type: "INIT_MEMORY",
|
|
63
|
-
memory: this.memoryManager.memory
|
|
64
|
-
}) : e.postMessage({ type: "INIT" }), this.slots.push(t), s.debug(`worker spawned, ${this.memoryManager ? "INIT_MEMORY" : "INIT"} sent`), t;
|
|
10
|
+
}, n = class extends Error {
|
|
11
|
+
constructor(e = "task timed out") {
|
|
12
|
+
super(e), this.name = "TaskTimeoutError";
|
|
65
13
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
handleWorkerError(e, t) {
|
|
71
|
-
if (this.closed) return;
|
|
72
|
-
let n = t, r = n?.message ?? (n?.error ? String(n.error) : "worker error");
|
|
73
|
-
s.warn(`worker error: ${r}`);
|
|
74
|
-
for (let [t, n] of [...this.pending]) if (n.slot === e) {
|
|
75
|
-
if (this.pending.delete(t), this.detach(n), n.done) continue;
|
|
76
|
-
n.done = !0, this.statsAgg.failed++, n.reject(new o(r));
|
|
77
|
-
}
|
|
78
|
-
let i = this.slots.indexOf(e);
|
|
79
|
-
if (i >= 0 && this.slots.splice(i, 1), e.worker.terminate(), this.respawn) s.info("respawning crashed worker"), this.addWorker(this.spawn());
|
|
80
|
-
else if (this.slots.length === 0) for (let e of this.queue.splice(0)) e.done = !0, this.detach(e), this.statsAgg.failed++, e.reject(new o(r));
|
|
81
|
-
this.drainQueue(), this.checkDrained();
|
|
82
|
-
}
|
|
83
|
-
pickWorker() {
|
|
84
|
-
let e;
|
|
85
|
-
for (let t of this.slots) t.inFlight >= this.concurrency || (!e || t.inFlight < e.inFlight) && (e = t);
|
|
86
|
-
return e;
|
|
87
|
-
}
|
|
88
|
-
drainQueue() {
|
|
89
|
-
let e;
|
|
90
|
-
for (; this.queue.length > 0 && (e = this.pickWorker());) {
|
|
91
|
-
let t = this.queue.shift();
|
|
92
|
-
t.done || this.send(e, t);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
send(e, t) {
|
|
96
|
-
let n = this.nextMessageId++;
|
|
97
|
-
t.messageId = n, t.slot = e, t.sentAt = performance.now(), this.statsAgg.waitMs.add(performance.now() - t.enqueuedAt), e.inFlight++, this.pending.set(n, t), c.debug(`→ ${t.contract.taskId}`, {
|
|
98
|
-
worker: this.slots.indexOf(e),
|
|
99
|
-
args: t.args
|
|
100
|
-
}), e.worker.postMessage({
|
|
101
|
-
type: "EXECUTE_TASK",
|
|
102
|
-
messageId: n,
|
|
103
|
-
taskId: t.contract.taskId,
|
|
104
|
-
args: t.args
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
detach(e) {
|
|
108
|
-
e.timer && clearTimeout(e.timer), e.signal && e.onSignalAbort && e.signal.removeEventListener("abort", e.onSignalAbort);
|
|
109
|
-
}
|
|
110
|
-
cancel(e, t) {
|
|
111
|
-
if (e.done) return;
|
|
112
|
-
let n = this.queue.indexOf(e);
|
|
113
|
-
n >= 0 && this.queue.splice(n, 1), e.done = !0, this.detach(e), this.statsAgg.aborted++, e.reject(t), this.drainQueue(), this.checkDrained();
|
|
114
|
-
}
|
|
115
|
-
checkDrained() {
|
|
116
|
-
if (this.queue.length === 0 && this.pending.size === 0) {
|
|
117
|
-
let e = this.drainWaiters.splice(0);
|
|
118
|
-
for (let t of e) t();
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
dispatch(e, t, n) {
|
|
122
|
-
return this.closed || this.slots.length === 0 ? Promise.reject(new o("WorkerPool has been terminated — no workers available.")) : n?.signal?.aborted ? Promise.reject(new i()) : new Promise((o, s) => {
|
|
123
|
-
let c = {
|
|
124
|
-
contract: e,
|
|
125
|
-
args: t,
|
|
126
|
-
enqueuedAt: performance.now(),
|
|
127
|
-
resolve: o,
|
|
128
|
-
reject: s,
|
|
129
|
-
done: !1,
|
|
130
|
-
signal: n?.signal
|
|
131
|
-
};
|
|
132
|
-
n?.signal && (c.onSignalAbort = () => this.cancel(c, new i()), n.signal.addEventListener("abort", c.onSignalAbort, { once: !0 }));
|
|
133
|
-
let l = n?.timeout ?? this.taskTimeout;
|
|
134
|
-
l !== void 0 && (c.timer = setTimeout(() => this.cancel(c, new a()), l));
|
|
135
|
-
let u = this.pickWorker();
|
|
136
|
-
if (u) this.send(u, c);
|
|
137
|
-
else if (this.queue.length >= this.maxQueue) {
|
|
138
|
-
c.done = !0, this.detach(c), s(new r());
|
|
139
|
-
return;
|
|
140
|
-
} else this.queue.push(c);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
runTask(e, ...t) {
|
|
144
|
-
return this.dispatch(e, t);
|
|
145
|
-
}
|
|
146
|
-
get sharedBuffer() {
|
|
147
|
-
return this.memoryManager?.getBuffer();
|
|
148
|
-
}
|
|
149
|
-
get workers() {
|
|
150
|
-
return this.slots.map((e) => e.worker);
|
|
151
|
-
}
|
|
152
|
-
stats() {
|
|
153
|
-
return {
|
|
154
|
-
workers: this.slots.length,
|
|
155
|
-
idle: this.slots.filter((e) => e.inFlight === 0).length,
|
|
156
|
-
inFlight: this.pending.size,
|
|
157
|
-
queued: this.queue.length,
|
|
158
|
-
completed: this.statsAgg.completed,
|
|
159
|
-
failed: this.statsAgg.failed,
|
|
160
|
-
aborted: this.statsAgg.aborted,
|
|
161
|
-
waitMs: {
|
|
162
|
-
count: this.statsAgg.waitMs.count,
|
|
163
|
-
mean: this.statsAgg.waitMs.mean,
|
|
164
|
-
max: this.statsAgg.waitMs.max
|
|
165
|
-
},
|
|
166
|
-
runMs: {
|
|
167
|
-
count: this.statsAgg.runMs.count,
|
|
168
|
-
mean: this.statsAgg.runMs.mean,
|
|
169
|
-
max: this.statsAgg.runMs.max
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
close() {
|
|
174
|
-
return this.queue.length === 0 && this.pending.size === 0 ? (this.terminate(), Promise.resolve()) : new Promise((e) => {
|
|
175
|
-
this.drainWaiters.push(() => {
|
|
176
|
-
this.terminate(), e();
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
terminate() {
|
|
181
|
-
this.closed = !0, this.persistence?.stop()?.catch?.((e) => s.warn("memory persistence stop failed", e)), s.info(`terminating ${this.slots.length} worker(s)`);
|
|
182
|
-
let e = new o("pool terminated");
|
|
183
|
-
for (let [, t] of this.pending) this.detach(t), !t.done && (t.done = !0, this.statsAgg.failed++, t.reject(e));
|
|
184
|
-
this.pending.clear();
|
|
185
|
-
for (let t of this.queue.splice(0)) t.done = !0, this.detach(t), this.statsAgg.failed++, t.reject(e);
|
|
186
|
-
for (let e of this.slots) e.worker.terminate();
|
|
187
|
-
this.slots = [], this.checkDrained();
|
|
14
|
+
}, r = class extends Error {
|
|
15
|
+
constructor(e = "worker crashed") {
|
|
16
|
+
super(e), this.name = "WorkerCrashedError";
|
|
188
17
|
}
|
|
189
18
|
};
|
|
190
19
|
//#endregion
|
|
191
|
-
export {
|
|
20
|
+
export { e as PoolQueueFullError, t as TaskAbortedError, n as TaskTimeoutError, r as WorkerCrashedError };
|