@atolljs/core 0.1.13 → 0.1.15
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/listSchema.js +1 -2
- package/dist/contract/memoryDevtools.js +2 -3
- package/dist/contract/msgpackrCodec.js +1 -2
- package/dist/contract/reef.js +1 -2
- package/dist/contract/sharedMemory.js +61 -62
- package/dist/contract/types.d.ts +24 -0
- package/dist/contract/zod.js +1 -2
- package/dist/devtools.js +32 -39
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -22
- package/dist/log.js +1 -2
- package/dist/observable.js +18 -19
- package/dist/pool/dedicatedWorker.d.ts +3 -0
- package/dist/pool/dedicatedWorker.js +72 -68
- package/dist/pool/devtoolsCommands.js +1 -2
- package/dist/pool/errors.js +1 -2
- package/dist/pool/memory.js +1 -2
- package/dist/pool/subWorkerClient.js +1 -2
- package/dist/pool/workerChannel.js +21 -22
- package/dist/pool/workerClient.d.ts +15 -1
- package/dist/pool/workerClient.js +14 -10
- package/dist/pool/workerPool.d.ts +3 -0
- package/dist/pool/workerPool.js +119 -121
- package/dist/reactive.js +1 -2
- package/dist/reactiveGraph.js +1 -2
- package/dist/service.js +1 -2
- package/dist/shared/sharedWorkerClient.js +2 -9
- package/dist/shared/sharedWorkerHost.js +22 -27
- package/dist/task.js +1 -2
- package/dist/testing/inProcessWorker.js +3 -8
- package/dist/userTiming.js +1 -2
- package/dist/worker/defineWorker.js +8 -9
- package/dist/worker/registry.js +1 -2
- package/dist/worker/workerBootstrap.js +40 -52
- package/package.json +11 -7
- package/src/contract/types.ts +26 -0
- package/src/index.ts +2 -0
- package/src/pool/dedicatedWorker.ts +19 -1
- package/src/pool/workerClient.ts +30 -1
- package/src/pool/workerPool.ts +11 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { TaskAbortedError as e, TaskTimeoutError as t, WorkerCrashedError as n } from "./errors.js";
|
|
2
|
+
import { devtoolsEnabled as r, emitDevtools as i, estimateCloneBytes as a, nextDevtoolsId as o } from "../devtools.js";
|
|
3
|
+
import { fmtBytes as s, scoped as c } from "../log.js";
|
|
4
4
|
import { measureTaskCall as l } from "../userTiming.js";
|
|
5
5
|
import { forwardWorkerDevtools as u, sendInit as d, setupChannelMemory as f } from "./workerChannel.js";
|
|
6
6
|
import { CHAOS_FAILURE as p, KILL_MESSAGE as m, applyChaos as h, registerDevtoolsRunner as g } from "./devtoolsCommands.js";
|
|
7
|
-
|
|
8
|
-
var _ =
|
|
7
|
+
// src/pool/dedicatedWorker.ts
|
|
8
|
+
var _ = c("worker"), v = c("task"), y = () => ({
|
|
9
9
|
count: 0,
|
|
10
10
|
mean: 0,
|
|
11
11
|
max: 0,
|
|
@@ -19,6 +19,7 @@ var _ = a("worker"), v = a("task"), y = () => ({
|
|
|
19
19
|
mem;
|
|
20
20
|
spawn;
|
|
21
21
|
respawn;
|
|
22
|
+
onLifecycle;
|
|
22
23
|
taskTimeout;
|
|
23
24
|
label;
|
|
24
25
|
worker = null;
|
|
@@ -35,20 +36,20 @@ var _ = a("worker"), v = a("task"), y = () => ({
|
|
|
35
36
|
aborted: 0,
|
|
36
37
|
runMs: y()
|
|
37
38
|
};
|
|
38
|
-
constructor(
|
|
39
|
-
if (this.mem = f(
|
|
40
|
-
let
|
|
41
|
-
this.spawn =
|
|
42
|
-
let
|
|
43
|
-
_.info(
|
|
39
|
+
constructor(e) {
|
|
40
|
+
if (this.mem = f(e, "DedicatedWorker"), !e.workerUrl && !e.createWorker) throw Error("DedicatedWorker requires either `workerUrl` or `createWorker` in its config.");
|
|
41
|
+
let t = e.workerUrl;
|
|
42
|
+
this.spawn = e.createWorker ?? (() => new Worker(t, { type: "module" })), this.respawn = e.respawn ?? !0, this.onLifecycle = e.onLifecycle, this.taskTimeout = e.taskTimeout, this.label = e.name, this.poolId = o("worker", e.name), this.sharedMemory = this.mem.sharedMemory, this.persistence = this.mem.persistence;
|
|
43
|
+
let n = this.mem.sharedMemory?.totalBytes ?? this.mem.injectedBuffer?.byteLength;
|
|
44
|
+
_.info(n === void 0 ? "spawning 1 dedicated worker, message-only" : `spawning 1 dedicated worker with ${s(n)} shared buffer`), i({
|
|
44
45
|
type: "pool:init",
|
|
45
46
|
poolId: this.poolId,
|
|
46
47
|
label: this.label,
|
|
47
48
|
poolSize: 1,
|
|
48
49
|
concurrency: 0,
|
|
49
|
-
memoryBytes:
|
|
50
|
+
memoryBytes: n,
|
|
50
51
|
dedicated: !0
|
|
51
|
-
}), this.attach(this.spawn()),
|
|
52
|
+
}), this.attach(this.spawn()), r() && (this.devtoolsOff = g({
|
|
52
53
|
poolId: this.poolId,
|
|
53
54
|
label: this.label,
|
|
54
55
|
dedicated: !0,
|
|
@@ -68,16 +69,12 @@ var _ = a("worker"), v = a("task"), y = () => ({
|
|
|
68
69
|
this.worker = e, e.addEventListener("message", (t) => {
|
|
69
70
|
if (this.worker !== e) return;
|
|
70
71
|
let n = t.data;
|
|
71
|
-
|
|
72
|
-
u(this.poolId, 0, n.event);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
this.handleReply(n);
|
|
72
|
+
n.type === "ATOLL_DEVTOOLS" && n.event ? u(this.poolId, 0, n.event) : this.handleReply(n);
|
|
76
73
|
}), e.addEventListener("error", (t) => {
|
|
77
74
|
this.worker === e && this.handleError(t);
|
|
78
75
|
});
|
|
79
|
-
let
|
|
80
|
-
_.debug(`dedicated worker spawned, ${
|
|
76
|
+
let t = d(e, this.mem);
|
|
77
|
+
_.debug(`dedicated worker spawned, ${t} sent`), i({
|
|
81
78
|
type: "worker:spawn",
|
|
82
79
|
poolId: this.poolId,
|
|
83
80
|
slot: 0
|
|
@@ -86,116 +83,124 @@ var _ = a("worker"), v = a("task"), y = () => ({
|
|
|
86
83
|
detach(e) {
|
|
87
84
|
e.timer && clearTimeout(e.timer), e.signal && e.onSignalAbort && e.signal.removeEventListener("abort", e.onSignalAbort);
|
|
88
85
|
}
|
|
89
|
-
handleReply(
|
|
90
|
-
let
|
|
91
|
-
if (
|
|
92
|
-
if (this.pending.delete(
|
|
93
|
-
|
|
94
|
-
let
|
|
95
|
-
this.statsAgg.runMs.add(
|
|
86
|
+
handleReply(e) {
|
|
87
|
+
let t = e.messageId === void 0 ? void 0 : this.pending.get(e.messageId);
|
|
88
|
+
if (t) {
|
|
89
|
+
if (this.pending.delete(e.messageId), this.detach(t), !t.done) {
|
|
90
|
+
t.done = !0;
|
|
91
|
+
let n = performance.now() - t.sentAt;
|
|
92
|
+
this.statsAgg.runMs.add(n), e.success ? (this.statsAgg.completed++, v.debug(`← ${t.contract.taskId} ok`), r() && (i({
|
|
96
93
|
type: "task:settle",
|
|
97
94
|
poolId: this.poolId,
|
|
98
|
-
callId:
|
|
99
|
-
taskId:
|
|
95
|
+
callId: t.callId,
|
|
96
|
+
taskId: t.contract.taskId,
|
|
100
97
|
outcome: "ok",
|
|
101
|
-
runMs:
|
|
102
|
-
resultBytes:
|
|
103
|
-
}), l(this.poolId,
|
|
98
|
+
runMs: n,
|
|
99
|
+
resultBytes: a(e.result)
|
|
100
|
+
}), l(this.poolId, t.contract.taskId, t.callId, 0, t.sentAt, t.sentAt + n, !0)), t.resolve(e.result)) : (this.statsAgg.failed++, v.warn(`← ${t.contract.taskId} failed: ${e.error}`), r() && (i({
|
|
104
101
|
type: "task:settle",
|
|
105
102
|
poolId: this.poolId,
|
|
106
|
-
callId:
|
|
107
|
-
taskId:
|
|
103
|
+
callId: t.callId,
|
|
104
|
+
taskId: t.contract.taskId,
|
|
108
105
|
outcome: "error",
|
|
109
|
-
runMs:
|
|
110
|
-
error:
|
|
111
|
-
}), l(this.poolId,
|
|
106
|
+
runMs: n,
|
|
107
|
+
error: e.error
|
|
108
|
+
}), l(this.poolId, t.contract.taskId, t.callId, 0, t.sentAt, t.sentAt + n, !1)), t.reject(Error(e.error)));
|
|
112
109
|
}
|
|
113
110
|
this.checkDrained();
|
|
114
111
|
}
|
|
115
112
|
}
|
|
113
|
+
lifecycle(e, t) {
|
|
114
|
+
this.onLifecycle?.({
|
|
115
|
+
type: e,
|
|
116
|
+
poolId: this.poolId,
|
|
117
|
+
slot: 0,
|
|
118
|
+
message: t
|
|
119
|
+
});
|
|
120
|
+
}
|
|
116
121
|
handleError(e) {
|
|
117
122
|
if (this.closed) return;
|
|
118
|
-
let
|
|
119
|
-
_.warn(`worker error: ${
|
|
123
|
+
let t = e, n = t?.message ?? (t?.error ? String(t.error) : "worker error");
|
|
124
|
+
_.warn(`worker error: ${n}`), i({
|
|
120
125
|
type: "worker:error",
|
|
121
126
|
poolId: this.poolId,
|
|
122
127
|
slot: 0,
|
|
123
|
-
message:
|
|
124
|
-
}), this.rejectPending(
|
|
128
|
+
message: n
|
|
129
|
+
}), this.lifecycle("error", n), this.rejectPending(n), this.worker?.terminate(), this.worker = null, this.respawn && (_.info("respawning crashed worker"), this.attach(this.spawn()), i({
|
|
125
130
|
type: "worker:respawn",
|
|
126
131
|
poolId: this.poolId,
|
|
127
132
|
slot: 0
|
|
128
|
-
})), this.checkDrained();
|
|
133
|
+
}), this.lifecycle("respawn")), this.checkDrained();
|
|
129
134
|
}
|
|
130
135
|
rejectPending(e) {
|
|
131
|
-
let
|
|
132
|
-
for (let
|
|
136
|
+
let t = new n(e);
|
|
137
|
+
for (let n of this.pending.values()) this.detach(n), n.done || (n.done = !0, this.statsAgg.failed++, i({
|
|
133
138
|
type: "task:settle",
|
|
134
139
|
poolId: this.poolId,
|
|
135
|
-
callId:
|
|
136
|
-
taskId:
|
|
140
|
+
callId: n.callId,
|
|
141
|
+
taskId: n.contract.taskId,
|
|
137
142
|
outcome: "crashed",
|
|
138
143
|
error: e
|
|
139
|
-
}),
|
|
144
|
+
}), n.reject(t));
|
|
140
145
|
this.pending.clear();
|
|
141
146
|
}
|
|
142
147
|
cancel(e, n) {
|
|
143
|
-
e.done || (e.done = !0, this.detach(e), this.statsAgg.aborted++,
|
|
148
|
+
e.done || (e.done = !0, this.detach(e), this.statsAgg.aborted++, i({
|
|
144
149
|
type: "task:settle",
|
|
145
150
|
poolId: this.poolId,
|
|
146
151
|
callId: e.callId,
|
|
147
152
|
taskId: e.contract.taskId,
|
|
148
|
-
outcome: n instanceof
|
|
153
|
+
outcome: n instanceof t ? "timeout" : "aborted"
|
|
149
154
|
}), e.reject(n));
|
|
150
155
|
}
|
|
151
156
|
checkDrained() {
|
|
152
157
|
if (!(this.pending.size > 0)) for (let e of this.drainWaiters.splice(0)) e();
|
|
153
158
|
}
|
|
154
|
-
dispatch(
|
|
159
|
+
dispatch(o, s, c) {
|
|
155
160
|
let l = this.worker;
|
|
156
|
-
return this.closed || l === null ? Promise.reject(new
|
|
161
|
+
return this.closed || l === null ? Promise.reject(new n("DedicatedWorker has been terminated — no worker available.")) : c?.signal?.aborted ? Promise.reject(new e()) : new Promise((n, u) => {
|
|
157
162
|
let d = {
|
|
158
|
-
contract:
|
|
163
|
+
contract: o,
|
|
159
164
|
callId: ++this.nextCallId,
|
|
160
165
|
sentAt: performance.now(),
|
|
161
|
-
resolve:
|
|
166
|
+
resolve: n,
|
|
162
167
|
reject: u,
|
|
163
168
|
done: !1,
|
|
164
|
-
signal:
|
|
169
|
+
signal: c?.signal
|
|
165
170
|
};
|
|
166
|
-
|
|
167
|
-
let f =
|
|
168
|
-
f !== void 0 && (d.timer = setTimeout(() => this.cancel(d, new
|
|
171
|
+
c?.signal && (d.onSignalAbort = () => this.cancel(d, new e()), c.signal.addEventListener("abort", d.onSignalAbort, { once: !0 }));
|
|
172
|
+
let f = c?.timeout ?? this.taskTimeout;
|
|
173
|
+
f !== void 0 && (d.timer = setTimeout(() => this.cancel(d, new t()), f));
|
|
169
174
|
let m = this.nextMessageId++;
|
|
170
|
-
this.pending.set(m, d), v.debug(`→ ${
|
|
175
|
+
this.pending.set(m, d), v.debug(`→ ${o.taskId}`, { args: s }), i({
|
|
171
176
|
type: "task:enqueue",
|
|
172
177
|
poolId: this.poolId,
|
|
173
178
|
callId: d.callId,
|
|
174
|
-
taskId:
|
|
175
|
-
}),
|
|
179
|
+
taskId: o.taskId
|
|
180
|
+
}), r() && i({
|
|
176
181
|
type: "task:dispatch",
|
|
177
182
|
poolId: this.poolId,
|
|
178
183
|
callId: d.callId,
|
|
179
|
-
taskId:
|
|
184
|
+
taskId: o.taskId,
|
|
180
185
|
slot: 0,
|
|
181
186
|
waitMs: 0,
|
|
182
|
-
argBytes:
|
|
187
|
+
argBytes: a(s)
|
|
183
188
|
});
|
|
184
189
|
let g = {
|
|
185
190
|
type: "EXECUTE_TASK",
|
|
186
191
|
messageId: m,
|
|
187
|
-
taskId:
|
|
188
|
-
args:
|
|
192
|
+
taskId: o.taskId,
|
|
193
|
+
args: s
|
|
189
194
|
};
|
|
190
195
|
this.chaos ? h(this.chaos, {
|
|
191
196
|
live: () => this.pending.get(m) === d,
|
|
192
197
|
fail: () => this.handleReply({
|
|
193
198
|
messageId: m,
|
|
194
|
-
taskId:
|
|
199
|
+
taskId: o.taskId,
|
|
195
200
|
success: !1,
|
|
196
201
|
error: p
|
|
197
202
|
}),
|
|
198
|
-
timeout: () => this.cancel(d, new
|
|
203
|
+
timeout: () => this.cancel(d, new t()),
|
|
199
204
|
post: () => l.postMessage(g)
|
|
200
205
|
}) : l.postMessage(g);
|
|
201
206
|
});
|
|
@@ -239,11 +244,10 @@ var _ = a("worker"), v = a("task"), y = () => ({
|
|
|
239
244
|
});
|
|
240
245
|
}
|
|
241
246
|
terminate() {
|
|
242
|
-
this.closed || (this.closed = !0, this.devtoolsOff?.(), this.persistence?.stop()?.catch?.((e) => _.warn("memory persistence stop failed", e)), _.info("terminating dedicated worker"),
|
|
247
|
+
this.closed || (this.closed = !0, this.lifecycle("terminate"), this.devtoolsOff?.(), this.persistence?.stop()?.catch?.((e) => _.warn("memory persistence stop failed", e)), _.info("terminating dedicated worker"), i({
|
|
243
248
|
type: "pool:terminate",
|
|
244
249
|
poolId: this.poolId
|
|
245
250
|
}), this.rejectPending("worker terminated"), this.worker?.terminate(), this.worker = null, this.checkDrained());
|
|
246
251
|
}
|
|
247
252
|
};
|
|
248
|
-
//#endregion
|
|
249
253
|
export { b as DedicatedWorker };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { registerDevtoolsCommand as e } from "../devtools.js";
|
|
2
|
-
|
|
2
|
+
// src/pool/devtoolsCommands.ts
|
|
3
3
|
var t = "chaos: injected failure", n = "killed from devtools", r = /* @__PURE__ */ new Map(), i = null, a = (e) => {
|
|
4
4
|
let t = e.poolId;
|
|
5
5
|
if (typeof t != "string") throw Error("poolId (string) is required");
|
|
@@ -55,5 +55,4 @@ var t = "chaos: injected failure", n = "killed from devtools", r = /* @__PURE__
|
|
|
55
55
|
}
|
|
56
56
|
}, e.delayMs ?? 0);
|
|
57
57
|
};
|
|
58
|
-
//#endregion
|
|
59
58
|
export { t as CHAOS_FAILURE, n as KILL_MESSAGE, d as applyChaos, s as parseChaos, u as registerDevtoolsRunner };
|
package/dist/pool/errors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/pool/errors.ts
|
|
2
2
|
var e = class extends Error {
|
|
3
3
|
constructor(e = "WorkerPool task queue is full") {
|
|
4
4
|
super(e), this.name = "PoolQueueFullError";
|
|
@@ -16,5 +16,4 @@ var e = class extends Error {
|
|
|
16
16
|
super(e), this.name = "WorkerCrashedError";
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
|
-
//#endregion
|
|
20
19
|
export { e as PoolQueueFullError, t as TaskAbortedError, n as TaskTimeoutError, r as WorkerCrashedError };
|
package/dist/pool/memory.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { connectWorker as e } from "./workerClient.js";
|
|
2
|
-
|
|
2
|
+
// src/pool/subWorkerClient.ts
|
|
3
3
|
function t(t) {
|
|
4
4
|
let n = { ...t };
|
|
5
5
|
return typeof t.worker != "function" && (n.worker = () => {
|
|
@@ -7,5 +7,4 @@ function t(t) {
|
|
|
7
7
|
return new Worker(t.worker, { type: "module" });
|
|
8
8
|
}), e(n);
|
|
9
9
|
}
|
|
10
|
-
//#endregion
|
|
11
10
|
export { t as connectSubWorker };
|
|
@@ -1,50 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
function r(
|
|
5
|
-
if ((
|
|
1
|
+
import { MemoryManager as e } from "./memory.js";
|
|
2
|
+
import { devtoolsEnabled as t, forwardDevtools as n } from "../devtools.js";
|
|
3
|
+
// src/pool/workerChannel.ts
|
|
4
|
+
function r(t, n) {
|
|
5
|
+
if ((t.sharedMemory || t.sharedBuffer !== void 0) && (typeof SharedArrayBuffer > "u" || typeof crossOriginIsolated < "u" && !crossOriginIsolated)) throw Error(`${n} requires SharedArrayBuffer in a cross-origin isolated context — serve the page with COOP/COEP headers.`);
|
|
6
6
|
let r;
|
|
7
|
-
if (
|
|
8
|
-
if (!
|
|
7
|
+
if (t.sharedBuffer !== void 0 && (r = typeof t.sharedBuffer == "function" ? t.sharedBuffer() : t.sharedBuffer, !r)) throw Error(`${n}: \`sharedBuffer\` resolved to an empty buffer — the producing pool or contract has no bound memory yet.`);
|
|
8
|
+
if (!t.sharedMemory) return {
|
|
9
9
|
sharedMemory: void 0,
|
|
10
10
|
injectedBuffer: r,
|
|
11
11
|
persistence: void 0
|
|
12
12
|
};
|
|
13
|
-
let i =
|
|
14
|
-
return r ? i.bind(r) : (a = new
|
|
13
|
+
let i = t.sharedMemory, a;
|
|
14
|
+
return r ? i.bind(r) : (a = new e(t.memory), a.ensureCapacity(i.totalBytes), i.bind(a.getBuffer())), {
|
|
15
15
|
sharedMemory: i,
|
|
16
16
|
memoryManager: a,
|
|
17
17
|
injectedBuffer: r,
|
|
18
|
-
persistence:
|
|
18
|
+
persistence: t.persistence?.(i)
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
function i(
|
|
22
|
-
return n.memoryManager ? (
|
|
21
|
+
function i(e, n) {
|
|
22
|
+
return n.memoryManager ? (e.postMessage({
|
|
23
23
|
type: "INIT_MEMORY",
|
|
24
24
|
memory: n.memoryManager.memory,
|
|
25
|
-
devtools:
|
|
26
|
-
}), "INIT_MEMORY") : n.injectedBuffer ? (
|
|
25
|
+
devtools: t()
|
|
26
|
+
}), "INIT_MEMORY") : n.injectedBuffer ? (e.postMessage({
|
|
27
27
|
type: "INIT_MEMORY",
|
|
28
28
|
buffer: n.injectedBuffer,
|
|
29
|
-
devtools:
|
|
30
|
-
}), "INIT_MEMORY") : (
|
|
29
|
+
devtools: t()
|
|
30
|
+
}), "INIT_MEMORY") : (e.postMessage({
|
|
31
31
|
type: "INIT",
|
|
32
|
-
devtools:
|
|
32
|
+
devtools: t()
|
|
33
33
|
}), "INIT");
|
|
34
34
|
}
|
|
35
|
-
function a(e,
|
|
36
|
-
let i = (
|
|
37
|
-
|
|
35
|
+
function a(e, t, r) {
|
|
36
|
+
let i = (n) => `${e}#${t}~${n}`;
|
|
37
|
+
n({
|
|
38
38
|
...r,
|
|
39
39
|
...r.poolId === void 0 ? {} : { poolId: i(r.poolId) },
|
|
40
40
|
worker: r.worker === void 0 ? {
|
|
41
41
|
poolId: e,
|
|
42
|
-
slot:
|
|
42
|
+
slot: t
|
|
43
43
|
} : {
|
|
44
44
|
poolId: i(r.worker.poolId),
|
|
45
45
|
slot: r.worker.slot
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
-
//#endregion
|
|
50
49
|
export { a as forwardWorkerDevtools, i as sendInit, r as setupChannelMemory };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ServiceMethod, TaskRunner } from '../service.js';
|
|
2
2
|
import type { SharedAccess, SharedMemory, SharedSpec } from '../contract/sharedMemory.js';
|
|
3
|
-
import type { MemoryConfig, Prettify } from '../contract/types.js';
|
|
3
|
+
import type { MemoryConfig, Prettify, WorkerLifecycleListener } from '../contract/types.js';
|
|
4
4
|
import { WorkerPool, type RunOptions } from './workerPool.js';
|
|
5
5
|
import { DedicatedWorker } from './dedicatedWorker.js';
|
|
6
6
|
import type { WorkerDefinition, WorkerMethodMap } from '../worker/defineWorker.js';
|
|
@@ -62,6 +62,13 @@ export interface ConnectWorkerConfig<S extends SharedSpec> {
|
|
|
62
62
|
maxQueue?: number;
|
|
63
63
|
/** Default true — a crashed worker is replaced and its in-flight calls reject. */
|
|
64
64
|
respawn?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Lifecycle observer — fires on worker 'error' (before respawn),
|
|
67
|
+
* 'respawn', and 'terminate'. For per-callsite subscriptions (island
|
|
68
|
+
* boundaries, telemetry) prefer `client.onWorkerEvent(fn)`, which can be
|
|
69
|
+
* attached after connect and supports multiple listeners.
|
|
70
|
+
*/
|
|
71
|
+
onLifecycle?: WorkerLifecycleListener;
|
|
65
72
|
/** Default per-call timeout ms (`client.with({ timeout })` overrides). */
|
|
66
73
|
taskTimeout?: number;
|
|
67
74
|
/** Default true: pool spawns on first method call (or start()). False spawns immediately. */
|
|
@@ -84,6 +91,13 @@ export type WorkerClient<W extends WorkerDefinition, S extends SharedSpec> = Wor
|
|
|
84
91
|
*/
|
|
85
92
|
readonly pool: WorkerRunner<S> | null;
|
|
86
93
|
readonly sharedMemory: ConnectWorkerConfig<S>['sharedMemory'] | undefined;
|
|
94
|
+
/**
|
|
95
|
+
* Subscribe to worker lifecycle transitions ('error' | 'respawn' |
|
|
96
|
+
* 'terminate') — the crash-detection seam error boundaries build on.
|
|
97
|
+
* Listeners attach lazily: subscribing before the pool spawns still sees
|
|
98
|
+
* every event. Returns the unsubscribe.
|
|
99
|
+
*/
|
|
100
|
+
onWorkerEvent(fn: WorkerLifecycleListener): () => void;
|
|
87
101
|
};
|
|
88
102
|
/**
|
|
89
103
|
* One-line worker hookup for the main thread — the WorkerPool boilerplate
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WorkerPool as e } from "./workerPool.js";
|
|
2
2
|
import { DedicatedWorker as t } from "./dedicatedWorker.js";
|
|
3
|
-
|
|
3
|
+
// src/pool/workerClient.ts
|
|
4
4
|
var n = (e, t, r, i) => {
|
|
5
5
|
let a = /* @__PURE__ */ new Map();
|
|
6
6
|
return new Proxy(() => {}, {
|
|
@@ -23,27 +23,31 @@ function r(e) {
|
|
|
23
23
|
}
|
|
24
24
|
var i = (e) => e === "auto" ? typeof navigator < "u" ? navigator.hardwareConcurrency ?? 4 : 4 : Math.max(1, Math.floor(e ?? 1));
|
|
25
25
|
function a(r) {
|
|
26
|
-
let a = null, o = () => {
|
|
26
|
+
let a = null, o = /* @__PURE__ */ new Set(), s = () => {
|
|
27
27
|
if (a) return a;
|
|
28
|
-
let n = i(r.workers ?? r.poolSize),
|
|
28
|
+
let n = i(r.workers ?? r.poolSize), s = {
|
|
29
29
|
sharedMemory: r.sharedMemory,
|
|
30
30
|
sharedBuffer: r.sharedBuffer,
|
|
31
31
|
createWorker: typeof r.worker == "function" ? r.worker : () => new Worker(r.worker, { type: "module" }),
|
|
32
32
|
name: r.name,
|
|
33
33
|
memory: r.memory,
|
|
34
34
|
respawn: r.respawn,
|
|
35
|
-
taskTimeout: r.taskTimeout
|
|
35
|
+
taskTimeout: r.taskTimeout,
|
|
36
|
+
onLifecycle: (e) => {
|
|
37
|
+
r.onLifecycle?.(e);
|
|
38
|
+
for (let t of [...o]) t(e);
|
|
39
|
+
}
|
|
36
40
|
};
|
|
37
|
-
return a = n === 1 ? new t(
|
|
38
|
-
...
|
|
41
|
+
return a = n === 1 ? new t(s) : new e({
|
|
42
|
+
...s,
|
|
39
43
|
poolSize: n,
|
|
40
44
|
concurrency: r.concurrency,
|
|
41
45
|
maxQueue: r.maxQueue
|
|
42
46
|
}), a;
|
|
43
47
|
};
|
|
44
|
-
return r.lazy === !1 &&
|
|
48
|
+
return r.lazy === !1 && s(), n([], s, {
|
|
45
49
|
start: () => {
|
|
46
|
-
|
|
50
|
+
s();
|
|
47
51
|
},
|
|
48
52
|
terminate: () => {
|
|
49
53
|
a?.terminate(), a = null;
|
|
@@ -53,8 +57,8 @@ function a(r) {
|
|
|
53
57
|
},
|
|
54
58
|
get sharedMemory() {
|
|
55
59
|
return r.sharedMemory;
|
|
56
|
-
}
|
|
60
|
+
},
|
|
61
|
+
onWorkerEvent: (e) => (o.add(e), () => o.delete(e))
|
|
57
62
|
});
|
|
58
63
|
}
|
|
59
|
-
//#endregion
|
|
60
64
|
export { a as connectWorker, i as resolveWorkerCount, r as workerClient };
|
|
@@ -51,6 +51,7 @@ declare class WorkerPoolImpl<S extends SharedSpec = SharedSpec> {
|
|
|
51
51
|
private readonly concurrency;
|
|
52
52
|
private readonly maxQueue;
|
|
53
53
|
private readonly respawn;
|
|
54
|
+
private readonly onLifecycle?;
|
|
54
55
|
private readonly taskTimeout?;
|
|
55
56
|
private closed;
|
|
56
57
|
/** Dashboard fault injection (`pool.chaos`); null unless devtools set it. */
|
|
@@ -72,6 +73,8 @@ declare class WorkerPoolImpl<S extends SharedSpec = SharedSpec> {
|
|
|
72
73
|
/** Adds a spawned worker: one message + one error listener, then the INIT handshake. */
|
|
73
74
|
private addWorker;
|
|
74
75
|
private handleReply;
|
|
76
|
+
/** Fan out a lifecycle transition — error boundaries subscribe through this. */
|
|
77
|
+
private lifecycle;
|
|
75
78
|
private handleWorkerError;
|
|
76
79
|
/** Least-busy worker below its concurrency cap, if any. */
|
|
77
80
|
private pickWorker;
|