@atolljs/core 0.1.6 → 0.1.8

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.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/dist/contract/memoryDevtools.d.ts +30 -0
  3. package/dist/contract/sharedMemory.d.ts +6 -2
  4. package/dist/contract/types.d.ts +13 -0
  5. package/dist/devtools.d.ts +346 -0
  6. package/dist/index.d.ts +8 -2
  7. package/dist/index.js +19 -16
  8. package/dist/index10.js +28 -184
  9. package/dist/index11.js +18 -48
  10. package/dist/index12.js +29 -63
  11. package/dist/index13.js +49 -24
  12. package/dist/index14.js +57 -59
  13. package/dist/index15.js +335 -45
  14. package/dist/index16.js +242 -47
  15. package/dist/index17.js +57 -38
  16. package/dist/index18.js +9 -86
  17. package/dist/index19.js +58 -48
  18. package/dist/index20.js +24 -56
  19. package/dist/index21.js +61 -0
  20. package/dist/index22.js +48 -0
  21. package/dist/index23.js +68 -0
  22. package/dist/index24.js +41 -0
  23. package/dist/index25.js +46 -0
  24. package/dist/index26.js +113 -0
  25. package/dist/index27.js +60 -0
  26. package/dist/index28.js +57 -0
  27. package/dist/index5.js +227 -35
  28. package/dist/index6.js +143 -471
  29. package/dist/index7.js +51 -20
  30. package/dist/index8.js +507 -27
  31. package/dist/index9.js +19 -18
  32. package/dist/pool/dedicatedWorker.d.ts +63 -0
  33. package/dist/pool/devtoolsCommands.d.ts +54 -0
  34. package/dist/pool/subWorkerClient.d.ts +50 -0
  35. package/dist/pool/workerChannel.d.ts +44 -0
  36. package/dist/pool/workerClient.d.ts +38 -8
  37. package/dist/pool/workerPool.d.ts +13 -0
  38. package/dist/reactive.d.ts +15 -0
  39. package/dist/reactiveGraph.d.ts +9 -0
  40. package/dist/userTiming.d.ts +27 -0
  41. package/package.json +2 -1
  42. package/src/contract/memoryDevtools.ts +249 -0
  43. package/src/contract/sharedMemory.ts +39 -9
  44. package/src/contract/types.ts +13 -0
  45. package/src/devtools.ts +735 -0
  46. package/src/index.ts +27 -1
  47. package/src/log.ts +22 -0
  48. package/src/observable.ts +16 -3
  49. package/src/pool/dedicatedWorker.ts +332 -0
  50. package/src/pool/devtoolsCommands.ts +165 -0
  51. package/src/pool/subWorkerClient.ts +70 -0
  52. package/src/pool/workerChannel.ts +99 -0
  53. package/src/pool/workerClient.ts +64 -15
  54. package/src/pool/workerPool.ts +115 -36
  55. package/src/reactive.ts +87 -12
  56. package/src/reactiveGraph.ts +91 -0
  57. package/src/testing/inProcessWorker.ts +4 -3
  58. package/src/userTiming.ts +71 -0
  59. package/src/worker/workerBootstrap.ts +29 -3
package/dist/index15.js CHANGED
@@ -1,48 +1,338 @@
1
- import { scoped as e } from "./index5.js";
2
- import { TaskRegistry as t } from "./index13.js";
3
- //#region src/service.ts
4
- var n = e("service");
5
- function r(e = {}) {
6
- return e;
7
- }
8
- function i(e) {
9
- return e;
10
- }
11
- function a(e, t) {
12
- let n = {};
13
- for (let [r, i] of Object.entries(t)) {
14
- let t = "def" in i ? i.def : i;
15
- n[r] = {
16
- taskId: t.taskId ?? `${e}.${r}`,
17
- ...t.dataType ? { dataType: t.dataType } : {},
18
- ...t.argsSchema ? { argsSchema: t.argsSchema } : {},
19
- ...t.resultSchema ? { resultSchema: t.resultSchema } : {}
20
- };
1
+ import { devtoolsEnabled as e, emitDevtools as t, estimateCloneBytes as n, nextDevtoolsId as r } from "./index5.js";
2
+ import { fmtBytes as i, scoped as a } from "./index7.js";
3
+ import { PoolQueueFullError as o, TaskAbortedError as s, TaskTimeoutError as c, WorkerCrashedError as l } from "./index11.js";
4
+ import { measureTaskCall as u } from "./index12.js";
5
+ import { forwardWorkerDevtools as d, sendInit as f, setupChannelMemory as p } from "./index13.js";
6
+ import { CHAOS_FAILURE as m, KILL_MESSAGE as h, applyChaos as g, registerDevtoolsRunner as _ } from "./index14.js";
7
+ //#region src/pool/workerPool.ts
8
+ var v = a("pool"), y = a("task"), b = () => ({
9
+ count: 0,
10
+ mean: 0,
11
+ max: 0,
12
+ add(e) {
13
+ this.count++, this.mean += (e - this.mean) / this.count, e > this.max && (this.max = e);
21
14
  }
22
- return {
23
- name: e,
24
- tasks: n
25
- };
26
- }
27
- function o(e, r) {
28
- let i = Object.keys(e.tasks).filter((e) => {
29
- let t = r[e];
30
- return typeof t != "function" && typeof t?.run != "function";
31
- });
32
- if (i.length > 0) throw Error(`implementService("${e.name}"): missing handlers for ${i.join(", ")}`);
33
- for (let [n, i] of Object.entries(e.tasks)) {
34
- let e = r[n];
35
- t.register(i, typeof e == "function" ? e.bind(r) : e.run.bind(e));
36
- }
37
- return n.info(`service "${e.name}" implemented — ${Object.keys(e.tasks).join(", ")}`), {
38
- service: e,
39
- handlers: r
15
+ }), x = class {
16
+ slots = [];
17
+ pending = /* @__PURE__ */ new Map();
18
+ nextMessageId = 0;
19
+ queue = [];
20
+ mem;
21
+ memoryManager;
22
+ injectedBuffer;
23
+ workerUrl;
24
+ spawn;
25
+ concurrency;
26
+ maxQueue;
27
+ respawn;
28
+ taskTimeout;
29
+ closed = !1;
30
+ chaos = null;
31
+ devtoolsOff;
32
+ drainWaiters = [];
33
+ statsAgg = {
34
+ completed: 0,
35
+ failed: 0,
36
+ aborted: 0,
37
+ waitMs: b(),
38
+ runMs: b()
40
39
  };
41
- }
42
- function s(e, t) {
43
- let n = {};
44
- for (let [r, i] of Object.entries(e.tasks)) n[r] = (...e) => t.runTask(i, ...e);
45
- return n;
46
- }
40
+ poolId;
41
+ label;
42
+ nextCallId = 0;
43
+ sharedMemory;
44
+ persistence;
45
+ constructor(n) {
46
+ let a = p(n, "WorkerPool");
47
+ if (!n.workerUrl && !n.createWorker) throw Error("WorkerPool requires either `workerUrl` or `createWorker` in its config.");
48
+ this.workerUrl = n.workerUrl, this.spawn = n.createWorker ?? (() => new Worker(this.workerUrl, { type: "module" })), this.concurrency = n.concurrency ?? 1, this.maxQueue = n.maxQueue ?? Infinity, this.respawn = n.respawn ?? !0, this.taskTimeout = n.taskTimeout, this.label = n.name, this.poolId = r("pool", n.name), this.mem = a, this.sharedMemory = a.sharedMemory, this.memoryManager = a.memoryManager, this.injectedBuffer = a.injectedBuffer, this.persistence = a.persistence;
49
+ let o = n.poolSize ?? "auto", s = o === "auto" ? typeof navigator < "u" ? navigator.hardwareConcurrency ?? 4 : 4 : o;
50
+ v.info(this.sharedMemory ? `spawning ${s} worker(s) with ${i(this.sharedMemory.totalBytes)} shared buffer` : this.injectedBuffer ? `spawning ${s} worker(s) with ${i(this.injectedBuffer.byteLength)} inherited buffer` : `spawning ${s} worker(s), message-only`, { workerUrl: n.workerUrl ? String(n.workerUrl) : "createWorker()" }), v.debug("tip: setLogLevel('trace') for per-task detail"), t({
51
+ type: "pool:init",
52
+ poolId: this.poolId,
53
+ label: this.label,
54
+ poolSize: s,
55
+ concurrency: this.concurrency,
56
+ memoryBytes: this.sharedMemory?.totalBytes ?? this.injectedBuffer?.byteLength
57
+ });
58
+ for (let e = 0; e < s; e++) this.addWorker(this.spawn());
59
+ e() && (this.devtoolsOff = _({
60
+ poolId: this.poolId,
61
+ label: this.label,
62
+ dedicated: !1,
63
+ size: () => this.slots.length,
64
+ stats: () => this.stats(),
65
+ kill: (e) => {
66
+ let t = this.slots[e];
67
+ if (!t) throw Error(`${this.poolId} has no worker slot ${e}`);
68
+ this.handleWorkerError(t, { message: h });
69
+ },
70
+ getChaos: () => this.chaos,
71
+ setChaos: (e) => {
72
+ this.chaos = e;
73
+ }
74
+ }));
75
+ let c = n.tasks ?? {};
76
+ for (let [e, t] of Object.entries(c)) {
77
+ if (e in this) {
78
+ v.warn(`task "${e}" collides with a WorkerPool member — not installed`);
79
+ continue;
80
+ }
81
+ this[e] = (...e) => this.runTask(t, ...e);
82
+ }
83
+ Object.keys(c).length > 0 && v.info(`task methods: ${Object.keys(c).join(", ")}`);
84
+ }
85
+ addWorker(e) {
86
+ let n = {
87
+ worker: e,
88
+ inFlight: 0
89
+ };
90
+ e.addEventListener("message", (e) => {
91
+ let t = e.data;
92
+ if (t.type === "ATOLL_DEVTOOLS" && t.event) {
93
+ d(this.poolId, this.slots.indexOf(n), t.event);
94
+ return;
95
+ }
96
+ this.handleReply(n, t);
97
+ }), e.addEventListener("error", (e) => {
98
+ this.handleWorkerError(n, e);
99
+ });
100
+ let r = f(e, this.mem);
101
+ return this.slots.push(n), v.debug(`worker spawned, ${r} sent`), t({
102
+ type: "worker:spawn",
103
+ poolId: this.poolId,
104
+ slot: this.slots.indexOf(n)
105
+ }), n;
106
+ }
107
+ handleReply(r, i) {
108
+ let a = i.messageId === void 0 ? void 0 : this.pending.get(i.messageId);
109
+ if (a) {
110
+ if (this.pending.delete(i.messageId), r.inFlight--, this.detach(a), !a.done) {
111
+ a.done = !0;
112
+ let o = a.sentAt ?? a.enqueuedAt, s = performance.now() - o;
113
+ this.statsAgg.runMs.add(s), i.success ? (this.statsAgg.completed++, y.debug(`← ${a.contract.taskId} ok`), e() && (t({
114
+ type: "task:settle",
115
+ poolId: this.poolId,
116
+ callId: a.callId,
117
+ taskId: a.contract.taskId,
118
+ outcome: "ok",
119
+ runMs: s,
120
+ resultBytes: n(i.result)
121
+ }), u(this.poolId, a.contract.taskId, a.callId, this.slots.indexOf(r), o, o + s, !0)), a.resolve(i.result)) : (this.statsAgg.failed++, y.warn(`← ${a.contract.taskId} failed: ${i.error}`), e() && (t({
122
+ type: "task:settle",
123
+ poolId: this.poolId,
124
+ callId: a.callId,
125
+ taskId: a.contract.taskId,
126
+ outcome: "error",
127
+ runMs: s,
128
+ error: i.error
129
+ }), u(this.poolId, a.contract.taskId, a.callId, this.slots.indexOf(r), o, o + s, !1)), a.reject(Error(i.error)));
130
+ }
131
+ this.drainQueue(), this.checkDrained();
132
+ }
133
+ }
134
+ handleWorkerError(e, n) {
135
+ if (this.closed) return;
136
+ let r = n, i = r?.message ?? (r?.error ? String(r.error) : "worker error");
137
+ v.warn(`worker error: ${i}`);
138
+ let a = this.slots.indexOf(e);
139
+ t({
140
+ type: "worker:error",
141
+ poolId: this.poolId,
142
+ slot: a,
143
+ message: i
144
+ });
145
+ for (let [n, r] of [...this.pending]) if (r.slot === e) {
146
+ if (this.pending.delete(n), this.detach(r), r.done) continue;
147
+ r.done = !0, this.statsAgg.failed++, t({
148
+ type: "task:settle",
149
+ poolId: this.poolId,
150
+ callId: r.callId,
151
+ taskId: r.contract.taskId,
152
+ outcome: "crashed",
153
+ error: i
154
+ }), r.reject(new l(i));
155
+ }
156
+ if (a >= 0 && this.slots.splice(a, 1), e.worker.terminate(), this.respawn) v.info("respawning crashed worker"), this.addWorker(this.spawn()), t({
157
+ type: "worker:respawn",
158
+ poolId: this.poolId,
159
+ slot: this.slots.length - 1
160
+ });
161
+ else if (this.slots.length === 0) for (let e of this.queue.splice(0)) e.done = !0, this.detach(e), this.statsAgg.failed++, t({
162
+ type: "task:settle",
163
+ poolId: this.poolId,
164
+ callId: e.callId,
165
+ taskId: e.contract.taskId,
166
+ outcome: "crashed",
167
+ error: i
168
+ }), e.reject(new l(i));
169
+ this.drainQueue(), this.checkDrained();
170
+ }
171
+ pickWorker() {
172
+ let e;
173
+ for (let t of this.slots) t.inFlight >= this.concurrency || (!e || t.inFlight < e.inFlight) && (e = t);
174
+ return e;
175
+ }
176
+ drainQueue() {
177
+ let e;
178
+ for (; this.queue.length > 0 && (e = this.pickWorker());) {
179
+ let t = this.queue.shift();
180
+ t.done || this.send(e, t);
181
+ }
182
+ }
183
+ send(r, i) {
184
+ let a = this.nextMessageId++;
185
+ i.messageId = a, i.slot = r, i.sentAt = performance.now();
186
+ let o = i.sentAt - i.enqueuedAt;
187
+ this.statsAgg.waitMs.add(o), r.inFlight++, this.pending.set(a, i);
188
+ let s = this.slots.indexOf(r);
189
+ y.debug(`→ ${i.contract.taskId}`, {
190
+ worker: s,
191
+ args: i.args
192
+ }), e() && t({
193
+ type: "task:dispatch",
194
+ poolId: this.poolId,
195
+ callId: i.callId,
196
+ taskId: i.contract.taskId,
197
+ slot: s,
198
+ waitMs: o,
199
+ argBytes: n(i.args)
200
+ });
201
+ let l = {
202
+ type: "EXECUTE_TASK",
203
+ messageId: a,
204
+ taskId: i.contract.taskId,
205
+ args: i.args
206
+ };
207
+ this.chaos ? g(this.chaos, {
208
+ live: () => this.pending.get(a) === i,
209
+ fail: () => this.handleReply(r, {
210
+ messageId: a,
211
+ taskId: l.taskId,
212
+ success: !1,
213
+ error: m
214
+ }),
215
+ timeout: () => this.cancel(i, new c()),
216
+ post: () => r.worker.postMessage(l)
217
+ }) : r.worker.postMessage(l);
218
+ }
219
+ detach(e) {
220
+ e.timer && clearTimeout(e.timer), e.signal && e.onSignalAbort && e.signal.removeEventListener("abort", e.onSignalAbort);
221
+ }
222
+ cancel(e, n) {
223
+ if (e.done) return;
224
+ let r = this.queue.indexOf(e);
225
+ r >= 0 && this.queue.splice(r, 1), e.done = !0, this.detach(e), this.statsAgg.aborted++, t({
226
+ type: "task:settle",
227
+ poolId: this.poolId,
228
+ callId: e.callId,
229
+ taskId: e.contract.taskId,
230
+ outcome: n instanceof c ? "timeout" : "aborted"
231
+ }), e.reject(n), this.drainQueue(), this.checkDrained();
232
+ }
233
+ checkDrained() {
234
+ if (this.queue.length === 0 && this.pending.size === 0) {
235
+ let e = this.drainWaiters.splice(0);
236
+ for (let t of e) t();
237
+ }
238
+ }
239
+ dispatch(e, n, r) {
240
+ return this.closed || this.slots.length === 0 ? Promise.reject(new l("WorkerPool has been terminated — no workers available.")) : r?.signal?.aborted ? Promise.reject(new s()) : new Promise((i, a) => {
241
+ let l = {
242
+ contract: e,
243
+ args: n,
244
+ callId: ++this.nextCallId,
245
+ enqueuedAt: performance.now(),
246
+ resolve: i,
247
+ reject: a,
248
+ done: !1,
249
+ signal: r?.signal
250
+ };
251
+ r?.signal && (l.onSignalAbort = () => this.cancel(l, new s()), r.signal.addEventListener("abort", l.onSignalAbort, { once: !0 }));
252
+ let u = r?.timeout ?? this.taskTimeout;
253
+ u !== void 0 && (l.timer = setTimeout(() => this.cancel(l, new c()), u)), t({
254
+ type: "task:enqueue",
255
+ poolId: this.poolId,
256
+ callId: l.callId,
257
+ taskId: e.taskId
258
+ });
259
+ let d = this.pickWorker();
260
+ if (d) this.send(d, l);
261
+ else if (this.queue.length >= this.maxQueue) {
262
+ l.done = !0, this.detach(l), t({
263
+ type: "task:settle",
264
+ poolId: this.poolId,
265
+ callId: l.callId,
266
+ taskId: e.taskId,
267
+ outcome: "queue-full"
268
+ }), a(new o());
269
+ return;
270
+ } else this.queue.push(l);
271
+ });
272
+ }
273
+ runTask(e, ...t) {
274
+ return this.dispatch(e, t);
275
+ }
276
+ get sharedBuffer() {
277
+ return this.memoryManager?.getBuffer() ?? this.injectedBuffer;
278
+ }
279
+ get workers() {
280
+ return this.slots.map((e) => e.worker);
281
+ }
282
+ stats() {
283
+ return {
284
+ workers: this.slots.length,
285
+ idle: this.slots.filter((e) => e.inFlight === 0).length,
286
+ inFlight: this.pending.size,
287
+ queued: this.queue.length,
288
+ completed: this.statsAgg.completed,
289
+ failed: this.statsAgg.failed,
290
+ aborted: this.statsAgg.aborted,
291
+ waitMs: {
292
+ count: this.statsAgg.waitMs.count,
293
+ mean: this.statsAgg.waitMs.mean,
294
+ max: this.statsAgg.waitMs.max
295
+ },
296
+ runMs: {
297
+ count: this.statsAgg.runMs.count,
298
+ mean: this.statsAgg.runMs.mean,
299
+ max: this.statsAgg.runMs.max
300
+ }
301
+ };
302
+ }
303
+ close() {
304
+ return this.queue.length === 0 && this.pending.size === 0 ? (this.terminate(), Promise.resolve()) : new Promise((e) => {
305
+ this.drainWaiters.push(() => {
306
+ this.terminate(), e();
307
+ });
308
+ });
309
+ }
310
+ terminate() {
311
+ this.closed = !0, this.devtoolsOff?.(), this.persistence?.stop()?.catch?.((e) => v.warn("memory persistence stop failed", e)), v.info(`terminating ${this.slots.length} worker(s)`), t({
312
+ type: "pool:terminate",
313
+ poolId: this.poolId
314
+ });
315
+ let e = new l("pool terminated");
316
+ for (let [, n] of this.pending) this.detach(n), !n.done && (n.done = !0, this.statsAgg.failed++, t({
317
+ type: "task:settle",
318
+ poolId: this.poolId,
319
+ callId: n.callId,
320
+ taskId: n.contract.taskId,
321
+ outcome: "crashed",
322
+ error: "pool terminated"
323
+ }), n.reject(e));
324
+ this.pending.clear();
325
+ for (let n of this.queue.splice(0)) n.done = !0, this.detach(n), this.statsAgg.failed++, t({
326
+ type: "task:settle",
327
+ poolId: this.poolId,
328
+ callId: n.callId,
329
+ taskId: n.contract.taskId,
330
+ outcome: "crashed",
331
+ error: "pool terminated"
332
+ }), n.reject(e);
333
+ for (let e of this.slots) e.worker.terminate();
334
+ this.slots = [], this.checkDrained();
335
+ }
336
+ };
47
337
  //#endregion
48
- export { s as createClient, a as defineService, o as implementService, r as rpc, i as serviceMethod };
338
+ export { x as WorkerPool };
package/dist/index16.js CHANGED
@@ -1,54 +1,249 @@
1
- import { fmtBytes as e, scoped as t } from "./index5.js";
2
- import { bindSharedMemories as n, getDefinedSharedMemoryCount as r } from "./index6.js";
3
- import { TaskRegistry as i } from "./index13.js";
4
- //#region src/worker/workerBootstrap.ts
5
- var a = t("worker"), o = !1, s = !1;
6
- function c() {
7
- if (!s) {
8
- if (typeof self > "u") {
9
- a.warn("workerBootstrap: no worker global found — skipping message wiring");
10
- return;
11
- }
12
- s = !0, self.onmessage = async (t) => {
13
- let s = t.data;
14
- if (s.type === "INIT") {
15
- o = !0, r() > 0 && a.warn("pool sent INIT without memory, but shared-memory contracts are defined — field access will throw until bound"), a.info("initialized (message-only pool — no shared memory)");
16
- return;
1
+ import { devtoolsEnabled as e, emitDevtools as t, estimateCloneBytes as n, nextDevtoolsId as r } from "./index5.js";
2
+ import { fmtBytes as i, scoped as a } from "./index7.js";
3
+ import { TaskAbortedError as o, TaskTimeoutError as s, WorkerCrashedError as c } from "./index11.js";
4
+ import { measureTaskCall as l } from "./index12.js";
5
+ import { forwardWorkerDevtools as u, sendInit as d, setupChannelMemory as f } from "./index13.js";
6
+ import { CHAOS_FAILURE as p, KILL_MESSAGE as m, applyChaos as h, registerDevtoolsRunner as g } from "./index14.js";
7
+ //#region src/pool/dedicatedWorker.ts
8
+ var _ = a("worker"), v = a("task"), y = () => ({
9
+ count: 0,
10
+ mean: 0,
11
+ max: 0,
12
+ add(e) {
13
+ this.count++, this.mean += (e - this.mean) / this.count, e > this.max && (this.max = e);
14
+ }
15
+ }), b = class {
16
+ poolId;
17
+ sharedMemory;
18
+ persistence;
19
+ mem;
20
+ spawn;
21
+ respawn;
22
+ taskTimeout;
23
+ label;
24
+ worker = null;
25
+ pending = /* @__PURE__ */ new Map();
26
+ nextMessageId = 0;
27
+ nextCallId = 0;
28
+ closed = !1;
29
+ chaos = null;
30
+ devtoolsOff;
31
+ drainWaiters = [];
32
+ statsAgg = {
33
+ completed: 0,
34
+ failed: 0,
35
+ aborted: 0,
36
+ runMs: y()
37
+ };
38
+ constructor(n) {
39
+ if (this.mem = f(n, "DedicatedWorker"), !n.workerUrl && !n.createWorker) throw Error("DedicatedWorker requires either `workerUrl` or `createWorker` in its config.");
40
+ let a = n.workerUrl;
41
+ this.spawn = n.createWorker ?? (() => new Worker(a, { type: "module" })), this.respawn = n.respawn ?? !0, this.taskTimeout = n.taskTimeout, this.label = n.name, this.poolId = r("worker", n.name), this.sharedMemory = this.mem.sharedMemory, this.persistence = this.mem.persistence;
42
+ let o = this.mem.sharedMemory?.totalBytes ?? this.mem.injectedBuffer?.byteLength;
43
+ _.info(o === void 0 ? "spawning 1 dedicated worker, message-only" : `spawning 1 dedicated worker with ${i(o)} shared buffer`), t({
44
+ type: "pool:init",
45
+ poolId: this.poolId,
46
+ label: this.label,
47
+ poolSize: 1,
48
+ concurrency: 0,
49
+ memoryBytes: o,
50
+ dedicated: !0
51
+ }), this.attach(this.spawn()), e() && (this.devtoolsOff = g({
52
+ poolId: this.poolId,
53
+ label: this.label,
54
+ dedicated: !0,
55
+ size: () => +!!this.worker,
56
+ stats: () => this.stats(),
57
+ kill: (e) => {
58
+ if (e !== 0 || !this.worker) throw Error(`${this.poolId} has no live worker at slot ${e}`);
59
+ this.handleError({ message: m });
60
+ },
61
+ getChaos: () => this.chaos,
62
+ setChaos: (e) => {
63
+ this.chaos = e;
17
64
  }
18
- if (s.type === "INIT_MEMORY") {
19
- let t = s.memory;
20
- n(t.buffer), o = !0, a.info(`shared memory initialized (${e(t.buffer.byteLength)})`);
65
+ }));
66
+ }
67
+ attach(e) {
68
+ this.worker = e, e.addEventListener("message", (t) => {
69
+ if (this.worker !== e) return;
70
+ let n = t.data;
71
+ if (n.type === "ATOLL_DEVTOOLS" && n.event) {
72
+ u(this.poolId, 0, n.event);
21
73
  return;
22
74
  }
23
- if (s.type === "EXECUTE_TASK") {
24
- let { messageId: e, taskId: t, args: n } = s;
25
- if (!o) {
26
- a.warn(`rejected ${t} — shared memory not initialized`), self.postMessage({
27
- messageId: e,
28
- success: !1,
29
- error: "Shared memory not initialized in worker."
30
- });
31
- return;
32
- }
33
- let r = performance.now();
34
- try {
35
- let o = await i.execute(t, ...n || []);
36
- a.debug(`task ${t} → ${(performance.now() - r).toFixed(1)}ms`), self.postMessage({
37
- messageId: e,
38
- success: !0,
39
- result: o
40
- });
41
- } catch (n) {
42
- a.warn(`task ${t} failed: ${n.message || String(n)}`), self.postMessage({
43
- messageId: e,
44
- success: !1,
45
- error: n.message || String(n)
46
- });
47
- }
75
+ this.handleReply(n);
76
+ }), e.addEventListener("error", (t) => {
77
+ this.worker === e && this.handleError(t);
78
+ });
79
+ let n = d(e, this.mem);
80
+ _.debug(`dedicated worker spawned, ${n} sent`), t({
81
+ type: "worker:spawn",
82
+ poolId: this.poolId,
83
+ slot: 0
84
+ });
85
+ }
86
+ detach(e) {
87
+ e.timer && clearTimeout(e.timer), e.signal && e.onSignalAbort && e.signal.removeEventListener("abort", e.onSignalAbort);
88
+ }
89
+ handleReply(r) {
90
+ let i = r.messageId === void 0 ? void 0 : this.pending.get(r.messageId);
91
+ if (i) {
92
+ if (this.pending.delete(r.messageId), this.detach(i), !i.done) {
93
+ i.done = !0;
94
+ let a = performance.now() - i.sentAt;
95
+ this.statsAgg.runMs.add(a), r.success ? (this.statsAgg.completed++, v.debug(`← ${i.contract.taskId} ok`), e() && (t({
96
+ type: "task:settle",
97
+ poolId: this.poolId,
98
+ callId: i.callId,
99
+ taskId: i.contract.taskId,
100
+ outcome: "ok",
101
+ runMs: a,
102
+ resultBytes: n(r.result)
103
+ }), l(this.poolId, i.contract.taskId, i.callId, 0, i.sentAt, i.sentAt + a, !0)), i.resolve(r.result)) : (this.statsAgg.failed++, v.warn(`← ${i.contract.taskId} failed: ${r.error}`), e() && (t({
104
+ type: "task:settle",
105
+ poolId: this.poolId,
106
+ callId: i.callId,
107
+ taskId: i.contract.taskId,
108
+ outcome: "error",
109
+ runMs: a,
110
+ error: r.error
111
+ }), l(this.poolId, i.contract.taskId, i.callId, 0, i.sentAt, i.sentAt + a, !1)), i.reject(Error(r.error)));
112
+ }
113
+ this.checkDrained();
114
+ }
115
+ }
116
+ handleError(e) {
117
+ if (this.closed) return;
118
+ let n = e, r = n?.message ?? (n?.error ? String(n.error) : "worker error");
119
+ _.warn(`worker error: ${r}`), t({
120
+ type: "worker:error",
121
+ poolId: this.poolId,
122
+ slot: 0,
123
+ message: r
124
+ }), this.rejectPending(r), this.worker?.terminate(), this.worker = null, this.respawn && (_.info("respawning crashed worker"), this.attach(this.spawn()), t({
125
+ type: "worker:respawn",
126
+ poolId: this.poolId,
127
+ slot: 0
128
+ })), this.checkDrained();
129
+ }
130
+ rejectPending(e) {
131
+ let n = new c(e);
132
+ for (let r of this.pending.values()) this.detach(r), !r.done && (r.done = !0, this.statsAgg.failed++, t({
133
+ type: "task:settle",
134
+ poolId: this.poolId,
135
+ callId: r.callId,
136
+ taskId: r.contract.taskId,
137
+ outcome: "crashed",
138
+ error: e
139
+ }), r.reject(n));
140
+ this.pending.clear();
141
+ }
142
+ cancel(e, n) {
143
+ e.done || (e.done = !0, this.detach(e), this.statsAgg.aborted++, t({
144
+ type: "task:settle",
145
+ poolId: this.poolId,
146
+ callId: e.callId,
147
+ taskId: e.contract.taskId,
148
+ outcome: n instanceof s ? "timeout" : "aborted"
149
+ }), e.reject(n));
150
+ }
151
+ checkDrained() {
152
+ if (!(this.pending.size > 0)) for (let e of this.drainWaiters.splice(0)) e();
153
+ }
154
+ dispatch(r, i, a) {
155
+ let l = this.worker;
156
+ return this.closed || l === null ? Promise.reject(new c("DedicatedWorker has been terminated — no worker available.")) : a?.signal?.aborted ? Promise.reject(new o()) : new Promise((c, u) => {
157
+ let d = {
158
+ contract: r,
159
+ callId: ++this.nextCallId,
160
+ sentAt: performance.now(),
161
+ resolve: c,
162
+ reject: u,
163
+ done: !1,
164
+ signal: a?.signal
165
+ };
166
+ a?.signal && (d.onSignalAbort = () => this.cancel(d, new o()), a.signal.addEventListener("abort", d.onSignalAbort, { once: !0 }));
167
+ let f = a?.timeout ?? this.taskTimeout;
168
+ f !== void 0 && (d.timer = setTimeout(() => this.cancel(d, new s()), f));
169
+ let m = this.nextMessageId++;
170
+ this.pending.set(m, d), v.debug(`→ ${r.taskId}`, { args: i }), t({
171
+ type: "task:enqueue",
172
+ poolId: this.poolId,
173
+ callId: d.callId,
174
+ taskId: r.taskId
175
+ }), e() && t({
176
+ type: "task:dispatch",
177
+ poolId: this.poolId,
178
+ callId: d.callId,
179
+ taskId: r.taskId,
180
+ slot: 0,
181
+ waitMs: 0,
182
+ argBytes: n(i)
183
+ });
184
+ let g = {
185
+ type: "EXECUTE_TASK",
186
+ messageId: m,
187
+ taskId: r.taskId,
188
+ args: i
189
+ };
190
+ this.chaos ? h(this.chaos, {
191
+ live: () => this.pending.get(m) === d,
192
+ fail: () => this.handleReply({
193
+ messageId: m,
194
+ taskId: r.taskId,
195
+ success: !1,
196
+ error: p
197
+ }),
198
+ timeout: () => this.cancel(d, new s()),
199
+ post: () => l.postMessage(g)
200
+ }) : l.postMessage(g);
201
+ });
202
+ }
203
+ runTask(e, ...t) {
204
+ return this.dispatch(e, t);
205
+ }
206
+ get sharedBuffer() {
207
+ return this.mem.memoryManager?.getBuffer() ?? this.mem.injectedBuffer;
208
+ }
209
+ get workers() {
210
+ return this.worker ? [this.worker] : [];
211
+ }
212
+ stats() {
213
+ let e = +!!this.worker, { runMs: t } = this.statsAgg;
214
+ return {
215
+ workers: e,
216
+ idle: e && this.pending.size === 0 ? 1 : 0,
217
+ inFlight: this.pending.size,
218
+ queued: 0,
219
+ completed: this.statsAgg.completed,
220
+ failed: this.statsAgg.failed,
221
+ aborted: this.statsAgg.aborted,
222
+ waitMs: {
223
+ count: 0,
224
+ mean: 0,
225
+ max: 0
226
+ },
227
+ runMs: {
228
+ count: t.count,
229
+ mean: t.mean,
230
+ max: t.max
48
231
  }
49
232
  };
50
233
  }
51
- }
52
- c();
234
+ close() {
235
+ return this.pending.size === 0 ? (this.terminate(), Promise.resolve()) : new Promise((e) => {
236
+ this.drainWaiters.push(() => {
237
+ this.terminate(), e();
238
+ });
239
+ });
240
+ }
241
+ terminate() {
242
+ this.closed || (this.closed = !0, this.devtoolsOff?.(), this.persistence?.stop()?.catch?.((e) => _.warn("memory persistence stop failed", e)), _.info("terminating dedicated worker"), t({
243
+ type: "pool:terminate",
244
+ poolId: this.poolId
245
+ }), this.rejectPending("worker terminated"), this.worker?.terminate(), this.worker = null, this.checkDrained());
246
+ }
247
+ };
53
248
  //#endregion
54
- export { c as installWorkerListener };
249
+ export { b as DedicatedWorker };