@atolljs/core 0.1.7 → 0.1.9

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 +4 -2
  4. package/dist/contract/types.d.ts +11 -0
  5. package/dist/devtools.d.ts +127 -4
  6. package/dist/index.d.ts +8 -4
  7. package/dist/index.js +19 -17
  8. package/dist/index10.js +29 -14
  9. package/dist/index11.js +14 -307
  10. package/dist/index12.js +29 -50
  11. package/dist/index13.js +46 -60
  12. package/dist/index14.js +56 -22
  13. package/dist/index15.js +332 -55
  14. package/dist/index16.js +246 -45
  15. package/dist/index17.js +56 -51
  16. package/dist/index18.js +9 -39
  17. package/dist/index19.js +56 -80
  18. package/dist/index20.js +24 -53
  19. package/dist/index21.js +56 -52
  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 +135 -64
  28. package/dist/index6.js +160 -35
  29. package/dist/index7.js +45 -503
  30. package/dist/index8.js +513 -19
  31. package/dist/index9.js +19 -33
  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 +36 -8
  37. package/dist/pool/workerPool.d.ts +10 -1
  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 +4 -3
  42. package/src/contract/memoryDevtools.ts +249 -0
  43. package/src/contract/sharedMemory.ts +20 -6
  44. package/src/contract/types.ts +11 -0
  45. package/src/devtools.ts +251 -6
  46. package/src/index.ts +13 -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 +61 -15
  54. package/src/pool/workerPool.ts +75 -45
  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 +33 -5
package/dist/index15.js CHANGED
@@ -1,61 +1,338 @@
1
- import { fmtBytes as e, scoped as t } from "./index6.js";
2
- import { bindSharedMemories as n } from "./index7.js";
3
- import { MemoryManager as r } from "./index9.js";
4
- import { TaskRegistry as i } from "./index14.js";
5
- //#region src/shared/sharedWorkerHost.ts
6
- var a = t("shared-worker"), o = null, s = 0;
7
- function c(t, i) {
8
- if (o) return o.buffer.byteLength < t && a.warn(`client requested ${e(t)} but buffer is ${e(o.buffer.byteLength)} — first client's capacity wins`), o;
9
- let s = new r(i);
10
- return s.ensureCapacity(t), o = s.memory, n(s.getBuffer()), a.info(`shared buffer allocated (${e(o.buffer.byteLength)})`), o;
11
- }
12
- function l(e) {
13
- let t = ++s;
14
- a.info(`client #${t} connected`), e.onmessage = async (n) => {
15
- let r = n.data;
16
- if (r?.type === "SHARED_CONNECT") {
17
- let n = c(r.memoryBytes ?? 0, r.memory);
18
- e.postMessage({
19
- type: "SHARED_MEMORY",
20
- buffer: n.buffer,
21
- clientIndex: t
22
- });
23
- 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 { 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);
14
+ }
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()
39
+ };
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);
24
82
  }
25
- if (r?.type === "EXECUTE_TASK") {
26
- let { messageId: n, taskId: s, args: c } = r;
27
- if (!o) {
28
- e.postMessage({
29
- messageId: n,
30
- success: !1,
31
- error: "Shared memory not initialized in worker."
32
- });
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);
33
94
  return;
34
95
  }
35
- let l = performance.now();
36
- try {
37
- let r = await i.execute(s, ...c || []);
38
- a.debug(`task ${s} → ${(performance.now() - l).toFixed(1)}ms (client #${t})`), e.postMessage({
39
- messageId: n,
40
- success: !0,
41
- result: r
42
- });
43
- } catch (t) {
44
- a.warn(`task ${s} failed: ${t.message || String(t)}`), e.postMessage({
45
- messageId: n,
46
- success: !1,
47
- error: t.message || String(t)
48
- });
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)));
49
130
  }
50
- return;
131
+ this.drainQueue(), this.checkDrained();
51
132
  }
52
- r?.type === "SHARED_DISCONNECT" && (e.close(), a.info(`client #${t} disconnected`));
53
- }, e.start();
54
- }
55
- function u() {
56
- self.onconnect = (e) => {
57
- l(e.ports[0]);
58
- };
59
- }
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
+ };
60
337
  //#endregion
61
- export { l as attachSharedPort, u as sharedWorkerHost };
338
+ export { x as WorkerPool };
package/dist/index16.js CHANGED
@@ -1,48 +1,249 @@
1
- import { scoped as e } from "./index6.js";
2
- import { TaskRegistry as t } from "./index14.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 { 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);
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
+ }), 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()
40
37
  };
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
- }
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;
64
+ }
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);
73
+ return;
74
+ }
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
231
+ }
232
+ };
233
+ }
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
+ };
47
248
  //#endregion
48
- export { s as createClient, a as defineService, o as implementService, r as rpc, i as serviceMethod };
249
+ export { b as DedicatedWorker };