@systemfsoftware/effect-daemon-spec 0.5.0 → 0.7.0
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/effect-daemon-spec.d.ts +47 -27
- package/dist/index.d.ts +136 -119
- package/dist/index.mjs +417 -335
- package/package.json +8 -8
package/dist/index.mjs
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { Array as Array$1, Cause, Clock, Context, Duration, Effect, Either, Exit, Fiber, HashMap, Layer, Match, Metric, Option, Predicate, Ref, Schedule, Schema, Scope, Stream } from "effect";
|
|
2
|
-
import {
|
|
3
|
-
import "effect/
|
|
4
|
-
|
|
2
|
+
import { left, right } from "effect/Either";
|
|
3
|
+
import * as Match$1 from "effect/Match";
|
|
4
|
+
import * as S from "effect/Schema";
|
|
5
|
+
//#region src/backoff.kernel.ts
|
|
5
6
|
const cappedBackoff = (base, cap) => {
|
|
6
7
|
const ceiling = Duration.decode(cap);
|
|
7
8
|
return Schedule.exponential(base).pipe(Schedule.jittered, Schedule.modifyDelay((_, delay) => Duration.min(delay, ceiling)));
|
|
8
9
|
};
|
|
9
10
|
//#endregion
|
|
11
|
+
//#region src/brands.kernel.ts
|
|
12
|
+
const WorkerTypeId = Symbol.for("@systemfsoftware/effect-daemon/Worker");
|
|
13
|
+
const SupervisorTypeId = Symbol.for("@systemfsoftware/effect-daemon/Supervisor");
|
|
14
|
+
const DynamicSpecTypeId = Symbol.for("@systemfsoftware/effect-daemon/DynamicSpec");
|
|
15
|
+
//#endregion
|
|
10
16
|
//#region src/daemon-health.schema.ts
|
|
11
17
|
var DynamicLimitExceeded = class extends Schema.TaggedError()("DynamicLimitExceeded", { limit: Schema.Int.pipe(Schema.greaterThanOrEqualTo(0)) }) {};
|
|
12
18
|
//#endregion
|
|
13
|
-
//#region src/daemon-metrics.ts
|
|
19
|
+
//#region src/daemon-metrics.kernel.ts
|
|
14
20
|
const supervisorRestartsCounter = Metric.counter("app.daemon.supervisor.restart", { description: "Daemon supervisor restart count" });
|
|
15
21
|
const supervisorExhaustionsCounter = Metric.counter("app.daemon.supervisor.exhaustion", { description: "Daemon supervisor exhaustion count" });
|
|
16
22
|
const supervisorChildrenGauge = Metric.gauge("app.daemon.supervisor.children", { description: "Daemon supervisor current child count (dynamic supervisors only)" });
|
|
@@ -47,61 +53,75 @@ var TickPolicyConfig = class extends Schema.Class("TickPolicyConfig")({
|
|
|
47
53
|
startLogLevel: Schema.optional(Schema.Literal("debug", "info"))
|
|
48
54
|
}) {};
|
|
49
55
|
//#endregion
|
|
50
|
-
//#region src/daemon-reporter.ts
|
|
51
|
-
var DaemonReporter = class
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
//#endregion
|
|
58
|
-
//#region src/daemon-spec.ts
|
|
59
|
-
const WorkerTypeId = Symbol.for("@systemfsoftware/effect-daemon/Worker");
|
|
60
|
-
const SupervisorTypeId = Symbol.for("@systemfsoftware/effect-daemon/Supervisor");
|
|
61
|
-
const DynamicSpecTypeId = Symbol.for("@systemfsoftware/effect-daemon/DynamicSpec");
|
|
62
|
-
const isWorker = (x) => WorkerTypeId in x;
|
|
63
|
-
const isSupervisor = (x) => SupervisorTypeId in x;
|
|
56
|
+
//#region src/daemon-reporter.adapter.ts
|
|
57
|
+
var DaemonReporter = class extends Context.Tag("@systemfsoftware/effect-daemon-spec/daemon-reporter.adapter/DaemonReporter")() {};
|
|
58
|
+
const Noop = Layer.succeed(DaemonReporter, DaemonReporter.of({
|
|
59
|
+
onRestart: () => Effect.void,
|
|
60
|
+
onExhausted: () => Effect.void
|
|
61
|
+
}));
|
|
64
62
|
//#endregion
|
|
65
|
-
//#region src/daemon.ts
|
|
66
|
-
const
|
|
67
|
-
[WorkerTypeId]: WorkerTypeId,
|
|
68
|
-
name: common.name,
|
|
69
|
-
loop,
|
|
70
|
-
child: common.child ?? {},
|
|
71
|
-
tick: common.tick,
|
|
72
|
-
tickHooks: common.tickHooks ?? {},
|
|
73
|
-
lock: common.lock
|
|
74
|
-
});
|
|
75
|
-
const poll = (opts) => {
|
|
63
|
+
//#region src/daemon-poll.kernel.ts
|
|
64
|
+
const poll$1 = (opts) => {
|
|
76
65
|
if (typeof opts.prereq === "undefined") {
|
|
77
66
|
const gate = Effect.succeed(Option.some(Effect.asVoid(opts.work)));
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
67
|
+
return {
|
|
68
|
+
[WorkerTypeId]: WorkerTypeId,
|
|
69
|
+
name: opts.name,
|
|
70
|
+
loop: {
|
|
71
|
+
_tag: "Poll",
|
|
72
|
+
gate,
|
|
73
|
+
interval: opts.interval
|
|
74
|
+
},
|
|
75
|
+
child: opts.child ?? {},
|
|
76
|
+
tick: opts.tick,
|
|
77
|
+
tickHooks: opts.tickHooks ?? {},
|
|
78
|
+
lock: opts.lock
|
|
79
|
+
};
|
|
83
80
|
}
|
|
84
81
|
const { prereq, work } = opts;
|
|
85
82
|
const gate = Effect.map(prereq, Option.map((data) => work(data)));
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
return {
|
|
84
|
+
[WorkerTypeId]: WorkerTypeId,
|
|
85
|
+
name: opts.name,
|
|
86
|
+
loop: {
|
|
87
|
+
_tag: "Poll",
|
|
88
|
+
gate,
|
|
89
|
+
interval: opts.interval
|
|
90
|
+
},
|
|
91
|
+
child: opts.child ?? {},
|
|
92
|
+
tick: opts.tick,
|
|
93
|
+
tickHooks: opts.tickHooks ?? {},
|
|
94
|
+
lock: opts.lock
|
|
95
|
+
};
|
|
91
96
|
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/daemon-stream.kernel.ts
|
|
99
|
+
const stream$1 = (opts) => ({
|
|
100
|
+
[WorkerTypeId]: WorkerTypeId,
|
|
101
|
+
name: opts.name,
|
|
102
|
+
loop: {
|
|
103
|
+
_tag: "Stream",
|
|
104
|
+
stream: opts.stream
|
|
105
|
+
},
|
|
106
|
+
child: opts.child ?? {},
|
|
107
|
+
tick: opts.tick,
|
|
108
|
+
tickHooks: opts.tickHooks ?? {},
|
|
109
|
+
lock: opts.lock
|
|
95
110
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/daemon-subscription.kernel.ts
|
|
113
|
+
const subscription$1 = (opts) => ({
|
|
114
|
+
[WorkerTypeId]: WorkerTypeId,
|
|
115
|
+
name: opts.name,
|
|
116
|
+
loop: {
|
|
117
|
+
_tag: "Subscription",
|
|
118
|
+
acquire: Effect.asVoid(opts.acquire)
|
|
119
|
+
},
|
|
120
|
+
child: opts.child ?? {},
|
|
121
|
+
tick: opts.tick,
|
|
122
|
+
tickHooks: opts.tickHooks ?? {},
|
|
123
|
+
lock: opts.lock
|
|
99
124
|
});
|
|
100
|
-
const Daemon = {
|
|
101
|
-
poll,
|
|
102
|
-
stream,
|
|
103
|
-
subscription
|
|
104
|
-
};
|
|
105
125
|
//#endregion
|
|
106
126
|
//#region src/leader-lock.schema.ts
|
|
107
127
|
var LeaderLockNotAcquired = class extends Schema.TaggedError()("LeaderLockNotAcquired", { key: Schema.String }) {};
|
|
@@ -110,24 +130,17 @@ var LeaderLockInfraError = class extends Schema.TaggedError()("LeaderLockInfraEr
|
|
|
110
130
|
cause: Schema.Unknown
|
|
111
131
|
}) {};
|
|
112
132
|
//#endregion
|
|
113
|
-
//#region src/leader-lock.ts
|
|
114
|
-
var LeaderLock = class LeaderLock extends Context.Tag("@systemfsoftware/effect-daemon-spec/leader-lock/LeaderLock")() {
|
|
115
|
-
static Noop = Layer.succeed(LeaderLock, LeaderLock.of({ withLock: (_key, self) => Effect.map(self, Option.some) }));
|
|
116
|
-
};
|
|
117
|
-
const withLeaderLock = dual(2, (self, options) => Effect.gen(function* () {
|
|
118
|
-
const out = yield* (yield* LeaderLock).withLock(options.key, self);
|
|
119
|
-
if (Option.isSome(out)) return out.value;
|
|
120
|
-
return yield* Match.value(options.mode).pipe(Match.when("required", () => Effect.fail(new LeaderLockNotAcquired({ key: options.key }))), Match.when("optional", () => Effect.void), Match.exhaustive);
|
|
121
|
-
}));
|
|
122
|
-
//#endregion
|
|
123
133
|
//#region src/lock-primitive.schema.ts
|
|
124
134
|
var LockPrimitiveError = class extends Schema.TaggedError()("LockPrimitiveError", {
|
|
125
135
|
key: Schema.String,
|
|
126
136
|
cause: Schema.Unknown
|
|
127
137
|
}) {};
|
|
128
138
|
//#endregion
|
|
129
|
-
//#region src/lock
|
|
130
|
-
var
|
|
139
|
+
//#region src/leader-lock.adapter.ts
|
|
140
|
+
var LeaderLock = class LeaderLock extends Context.Tag("@systemfsoftware/effect-daemon-spec/leader-lock.adapter/LeaderLock")() {
|
|
141
|
+
static Noop = Layer.succeed(LeaderLock, LeaderLock.of({ withLock: (_key, self) => Effect.map(self, Option.some) }));
|
|
142
|
+
};
|
|
143
|
+
var LockPrimitive = class extends Context.Tag("@systemfsoftware/effect-daemon-spec/leader-lock.adapter/LockPrimitive")() {};
|
|
131
144
|
const LeaderLockFromPrimitive = Layer.effect(LeaderLock, Effect.gen(function* () {
|
|
132
145
|
const primitive = yield* LockPrimitive;
|
|
133
146
|
return LeaderLock.of({ withLock: (key, self) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
@@ -144,13 +157,163 @@ const LeaderLockFromPrimitive = Layer.effect(LeaderLock, Effect.gen(function* ()
|
|
|
144
157
|
})) });
|
|
145
158
|
}));
|
|
146
159
|
//#endregion
|
|
147
|
-
//#region src/internal/
|
|
160
|
+
//#region src/internal/allocate-worker-health.kernel.ts
|
|
161
|
+
const allocateWorkerHealth = (name) => Effect.gen(function* () {
|
|
162
|
+
const ready = yield* Effect.makeLatch(false);
|
|
163
|
+
const healthy = yield* Effect.makeLatch(true);
|
|
164
|
+
const paused = yield* Effect.makeLatch(true);
|
|
165
|
+
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "ready"), 0);
|
|
166
|
+
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "healthy"), 1);
|
|
167
|
+
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "paused"), 1);
|
|
168
|
+
return {
|
|
169
|
+
name,
|
|
170
|
+
ready,
|
|
171
|
+
healthy,
|
|
172
|
+
paused
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/internal/build-worker-loop.kernel.ts
|
|
177
|
+
const applySpanAttributes = (hooks) => {
|
|
178
|
+
const { spanAttributes } = hooks;
|
|
179
|
+
return (effect) => {
|
|
180
|
+
if (typeof spanAttributes === "undefined") return effect;
|
|
181
|
+
return Effect.tap(effect, () => Effect.orElse(spanAttributes, () => Effect.succeed({})).pipe(Effect.flatMap(Effect.annotateCurrentSpan)));
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const applyTrackDuration = (hooks) => {
|
|
185
|
+
const { trackDuration } = hooks;
|
|
186
|
+
return (effect) => {
|
|
187
|
+
if (typeof trackDuration === "undefined") return effect;
|
|
188
|
+
return Metric.trackDuration(effect, trackDuration);
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
const applyTimeout = (timeout) => (effect) => Effect.timeoutFail(effect, {
|
|
192
|
+
duration: timeout,
|
|
193
|
+
onTimeout: () => new Cause.TimeoutException()
|
|
194
|
+
});
|
|
195
|
+
const applyInnerRetry = (hooks) => {
|
|
196
|
+
const { innerRetry } = hooks;
|
|
197
|
+
return (effect) => {
|
|
198
|
+
if (typeof innerRetry === "undefined") return effect;
|
|
199
|
+
return Effect.retry(effect, innerRetry);
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
const openReadyGauge = (gauge, name) => Metric.set(Metric.tagged(Metric.tagged(gauge, "daemon", name), "latch", "ready"), 1);
|
|
203
|
+
const buildPollTick = (worker, health, gate, readyGauge) => {
|
|
204
|
+
const spanName = worker.tick.spanName ?? "daemon.tick";
|
|
205
|
+
const withSpanAttrs = applySpanAttributes(worker.tickHooks);
|
|
206
|
+
const withDuration = applyTrackDuration(worker.tickHooks);
|
|
207
|
+
const withTimeout = applyTimeout(worker.tick.tickTimeout);
|
|
208
|
+
const withInnerRetry = applyInnerRetry(worker.tickHooks);
|
|
209
|
+
const runWork = (work) => work.pipe(withSpanAttrs, withDuration, Effect.withSpan(spanName, {
|
|
210
|
+
root: true,
|
|
211
|
+
attributes: { "daemon.name": worker.name }
|
|
212
|
+
}), Effect.withLogSpan(spanName));
|
|
213
|
+
return withInnerRetry(withTimeout(Effect.andThen(health.paused.await, gate).pipe(Effect.flatMap(Option.match({
|
|
214
|
+
onNone: () => Effect.void,
|
|
215
|
+
onSome: runWork
|
|
216
|
+
}))))).pipe(Effect.tap(() => Effect.zipRight(health.ready.open, openReadyGauge(readyGauge, worker.name))), Effect.asVoid);
|
|
217
|
+
};
|
|
218
|
+
const wrapSpan = (worker, effect, spanName) => {
|
|
219
|
+
const finalSpan = spanName ?? "daemon.worker";
|
|
220
|
+
return effect.pipe(Effect.withSpan(finalSpan, {
|
|
221
|
+
root: true,
|
|
222
|
+
attributes: { "daemon.name": worker.name }
|
|
223
|
+
}), Effect.withLogSpan(finalSpan));
|
|
224
|
+
};
|
|
225
|
+
const buildPollLoop = (worker, loop, health, readyGauge) => {
|
|
226
|
+
const tick = buildPollTick(worker, health, loop.gate, readyGauge);
|
|
227
|
+
return Effect.repeat(tick, Schedule.spaced(loop.interval)).pipe(Effect.asVoid);
|
|
228
|
+
};
|
|
229
|
+
const propagateExit = (exit) => Match.value(exit).pipe(Match.tag("Failure", ({ cause }) => Effect.failCause(cause)), Match.orElse(() => Effect.void));
|
|
230
|
+
const buildStreamLoop = (worker, loop, health, readyGauge) => {
|
|
231
|
+
const body = Effect.gen(function* () {
|
|
232
|
+
yield* health.paused.await;
|
|
233
|
+
const fiber = yield* Effect.forkScoped(loop.stream.pipe(Stream.tap(() => Effect.zipRight(health.ready.open, openReadyGauge(readyGauge, worker.name))), Stream.runDrain));
|
|
234
|
+
const ready = applyTimeout(worker.tick.tickTimeout)(health.ready.await);
|
|
235
|
+
yield* Effect.raceFirst(ready, fiber.await.pipe(Effect.flatMap(propagateExit)));
|
|
236
|
+
yield* fiber.await.pipe(Effect.flatMap(propagateExit));
|
|
237
|
+
});
|
|
238
|
+
const retried = applyInnerRetry(worker.tickHooks)(body);
|
|
239
|
+
return wrapSpan(worker, retried.pipe(Effect.asVoid));
|
|
240
|
+
};
|
|
241
|
+
const buildSubscriptionLoop = (worker, loop, health, readyGauge) => {
|
|
242
|
+
const body = Effect.gen(function* () {
|
|
243
|
+
yield* health.paused.await;
|
|
244
|
+
yield* applyTimeout(worker.tick.tickTimeout)(loop.acquire);
|
|
245
|
+
yield* Effect.zipRight(health.ready.open, openReadyGauge(readyGauge, worker.name));
|
|
246
|
+
return yield* Effect.never;
|
|
247
|
+
});
|
|
248
|
+
return wrapSpan(worker, body.pipe(Effect.asVoid));
|
|
249
|
+
};
|
|
250
|
+
const buildWorkerLoop = (worker, health, readyGauge) => Match.value(worker.loop).pipe(Match.tag("Poll", (loop) => buildPollLoop(worker, loop, health, readyGauge)), Match.tag("Stream", (loop) => buildStreamLoop(worker, loop, health, readyGauge)), Match.tag("Subscription", (loop) => buildSubscriptionLoop(worker, loop, health, readyGauge)), Match.exhaustive);
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/internal/with-leader-lock.executor.ts
|
|
253
|
+
var WithLeaderLockExecutorDeps = class extends Context.Tag("WithLeaderLockExecutorDeps")() {};
|
|
254
|
+
function withLeaderLock(self, options) {
|
|
255
|
+
const acquire = Effect.gen(function* () {
|
|
256
|
+
const out = yield* (yield* WithLeaderLockExecutorDeps).withLock(options.key, self);
|
|
257
|
+
if (Option.isSome(out)) return out.value;
|
|
258
|
+
return yield* Match.value(options.mode).pipe(Match.when("required", () => Effect.fail(new LeaderLockNotAcquired({ key: options.key }))), Match.when("optional", () => Effect.void), Match.exhaustive);
|
|
259
|
+
});
|
|
260
|
+
const retryOnce = (current) => Effect.retry(acquire, {
|
|
261
|
+
schedule: current,
|
|
262
|
+
while: Predicate.isTagged("LeaderLockNotAcquired")
|
|
263
|
+
}).pipe(Effect.catchTag("LeaderLockNotAcquired", () => retryOnce(current)));
|
|
264
|
+
const backoff = options.acquireRetryBackoff;
|
|
265
|
+
if (options.mode === "required" && backoff !== null && typeof backoff === "object") return retryOnce(backoff);
|
|
266
|
+
return acquire;
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/leader-lock.kernel.ts
|
|
270
|
+
const isModeNone = (lock) => lock.mode === "none";
|
|
271
|
+
Context.Tag("DaemonWorkerExecutorDeps")();
|
|
272
|
+
const worker = (w) => Effect.gen(function* () {
|
|
273
|
+
const health = yield* allocateWorkerHealth(w.name);
|
|
274
|
+
const loop = buildWorkerLoop(w, health, healthStateGauge).pipe(Effect.orDie);
|
|
275
|
+
const lockNone = isModeNone(w.lock);
|
|
276
|
+
const required = !lockNone && w.lock.mode === "required";
|
|
277
|
+
let locked;
|
|
278
|
+
if (lockNone) locked = loop;
|
|
279
|
+
else if (required) locked = withLeaderLock(loop, {
|
|
280
|
+
key: w.lock.key,
|
|
281
|
+
mode: "required",
|
|
282
|
+
acquireRetryBackoff: w.lock.acquireRetryBackoff
|
|
283
|
+
});
|
|
284
|
+
else locked = withLeaderLock(loop, {
|
|
285
|
+
key: w.lock.key,
|
|
286
|
+
mode: "optional"
|
|
287
|
+
});
|
|
288
|
+
yield* Effect.forkScoped(locked.pipe(Effect.orDie));
|
|
289
|
+
return health;
|
|
290
|
+
});
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/internal/allocate-supervisor-health.kernel.ts
|
|
293
|
+
const allocateSupervisorHealth = (name, children) => Effect.gen(function* () {
|
|
294
|
+
const ready = yield* Effect.makeLatch(false);
|
|
295
|
+
const healthy = yield* Effect.makeLatch(true);
|
|
296
|
+
const paused = yield* Effect.makeLatch(true);
|
|
297
|
+
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "ready"), 0);
|
|
298
|
+
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "healthy"), 1);
|
|
299
|
+
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "paused"), 1);
|
|
300
|
+
return {
|
|
301
|
+
name,
|
|
302
|
+
ready,
|
|
303
|
+
healthy,
|
|
304
|
+
paused,
|
|
305
|
+
children
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/internal/intensity-window.kernel.ts
|
|
148
310
|
const isWithinWindow = (now, windowMillis) => (t) => now - t <= windowMillis;
|
|
149
|
-
const
|
|
311
|
+
const keepWithin = (now, windowMillis) => (ts) => ts.filter(isWithinWindow(now, windowMillis));
|
|
312
|
+
const pruneTimestamps = (ts, now, windowMillis) => keepWithin(now, windowMillis)(ts);
|
|
150
313
|
const recordTimestamp = (ts, now, windowMillis) => [now, ...pruneTimestamps(ts, now, windowMillis)];
|
|
151
314
|
const exceedsRestarts = (count, restarts) => count > restarts;
|
|
152
315
|
//#endregion
|
|
153
|
-
//#region src/internal/intensity.ts
|
|
316
|
+
//#region src/internal/intensity.kernel.ts
|
|
154
317
|
const neverExceeds = {
|
|
155
318
|
record: Effect.void,
|
|
156
319
|
isExceeded: Effect.succeed(false),
|
|
@@ -180,18 +343,10 @@ const boundedTracker = (restarts, window) => Effect.gen(function* () {
|
|
|
180
343
|
});
|
|
181
344
|
const make = (intensity) => Match.value(intensity).pipe(Match.tag("Unbounded", () => Effect.succeed(neverExceeds)), Match.tag("Bounded", ({ restarts, window }) => boundedTracker(restarts, window)), Match.exhaustive);
|
|
182
345
|
//#endregion
|
|
346
|
+
//#region src/internal/race-for-exit.kernel.ts
|
|
347
|
+
const raceForExit = (fibers) => Effect.raceAll(fibers.map((f, idx) => f.await.pipe(Effect.map((exit) => [idx, exit]))));
|
|
348
|
+
//#endregion
|
|
183
349
|
//#region src/internal/restart-decision.schema.ts
|
|
184
|
-
const RestartDecisionTypeId = Symbol.for("@systemfsoftware/effect-daemon/RestartDecision");
|
|
185
|
-
var Continue = class extends Schema.TaggedClass()("Continue", {}) {
|
|
186
|
-
[RestartDecisionTypeId] = RestartDecisionTypeId;
|
|
187
|
-
};
|
|
188
|
-
var Restart = class extends Schema.TaggedClass()("Restart", { indices: Schema.NonEmptyArray(Schema.Int) }) {
|
|
189
|
-
[RestartDecisionTypeId] = RestartDecisionTypeId;
|
|
190
|
-
};
|
|
191
|
-
var Exhausted = class extends Schema.TaggedClass()("Exhausted", {}) {
|
|
192
|
-
[RestartDecisionTypeId] = RestartDecisionTypeId;
|
|
193
|
-
};
|
|
194
|
-
Schema.Union(Continue, Restart, Exhausted);
|
|
195
350
|
const RestartStrategy = Schema.Literal("one_for_one", "one_for_all", "rest_for_one");
|
|
196
351
|
Schema.Struct({
|
|
197
352
|
strategy: RestartStrategy,
|
|
@@ -213,30 +368,24 @@ Schema.Struct({
|
|
|
213
368
|
intensityExceeded: fc.boolean()
|
|
214
369
|
}))) }));
|
|
215
370
|
//#endregion
|
|
216
|
-
//#region src/internal/restart-decision.
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return [failedIndex, ...Array.from({ length: Math.max(0, total - failedIndex - 1) }, (_, i) => failedIndex + 1 + i)];
|
|
371
|
+
//#region src/internal/restart-decision.workflow.ts
|
|
372
|
+
const RestartDecisionTypeId = Symbol.for("@systemfsoftware/effect-daemon/RestartDecision");
|
|
373
|
+
var RestartDecisionContinue = class extends S.TaggedClass()("Continue", {}) {
|
|
374
|
+
[RestartDecisionTypeId] = RestartDecisionTypeId;
|
|
221
375
|
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (input.intensityExceeded) return new Exhausted();
|
|
225
|
-
return new Restart({ indices: restartIndicesFor(input.strategy, input.failedIndex, input.totalChildren) });
|
|
376
|
+
var RestartDecisionRestart = class extends S.TaggedClass()("Restart", { indices: S.NonEmptyArray(S.Int) }) {
|
|
377
|
+
[RestartDecisionTypeId] = RestartDecisionTypeId;
|
|
226
378
|
};
|
|
379
|
+
var RestartDecisionExhausted = class extends S.TaggedError()("Exhausted", {}) {
|
|
380
|
+
[RestartDecisionTypeId] = RestartDecisionTypeId;
|
|
381
|
+
};
|
|
382
|
+
const restartIndicesFor = (strategy, failedIndex, total) => Match$1.value(strategy).pipe(Match$1.when("one_for_one", () => [failedIndex]), Match$1.when("one_for_all", () => [0, ...Array.from({ length: Math.max(0, total - 1) }, (_, i) => i + 1)]), Match$1.when("rest_for_one", () => [failedIndex, ...Array.from({ length: Math.max(0, total - failedIndex - 1) }, (_, i) => failedIndex + 1 + i)]), Match$1.exhaustive);
|
|
383
|
+
const decideRestart = (input) => Match$1.value(input).pipe(Match$1.when({ exitSuccess: true }, () => right(new RestartDecisionContinue())), Match$1.when({
|
|
384
|
+
exitSuccess: false,
|
|
385
|
+
intensityExceeded: true
|
|
386
|
+
}, () => left(new RestartDecisionExhausted())), Match$1.orElse(() => right(new RestartDecisionRestart({ indices: restartIndicesFor(input.strategy, input.failedIndex, input.totalChildren) }))));
|
|
227
387
|
//#endregion
|
|
228
|
-
//#region src/internal/
|
|
229
|
-
const failedIndexOf = (startIdx, failedOffset) => startIdx + failedOffset;
|
|
230
|
-
//#endregion
|
|
231
|
-
//#region src/internal/supervision-context.strategy.ts
|
|
232
|
-
const openAllReady = (ctx) => Effect.gen(function* () {
|
|
233
|
-
yield* Effect.yieldNow();
|
|
234
|
-
yield* Effect.forEach(ctx.booted, (b) => b.health.ready.await, { concurrency: "unbounded" });
|
|
235
|
-
yield* Effect.zipRight(ctx.health.ready.open, Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", ctx.name), "latch", "ready"), 1));
|
|
236
|
-
});
|
|
237
|
-
const raceForExit = (fibers) => Effect.raceAll(fibers.map((f, idx) => f.await.pipe(Effect.map((exit) => [idx, exit]))));
|
|
238
|
-
//#endregion
|
|
239
|
-
//#region src/internal/supervise-tree.strategy.ts
|
|
388
|
+
//#region src/internal/supervision-epoch.schema.ts
|
|
240
389
|
const EpochStepTypeId = Symbol.for("@systemfsoftware/effect-daemon/EpochStep");
|
|
241
390
|
var StopEpoch = class extends Schema.TaggedClass()("StopEpoch", {}) {
|
|
242
391
|
[EpochStepTypeId] = EpochStepTypeId;
|
|
@@ -256,6 +405,9 @@ var ContinueSupervision = class extends Schema.TaggedClass()("ContinueSupervisio
|
|
|
256
405
|
[SupervisionEpochResultTypeId] = SupervisionEpochResultTypeId;
|
|
257
406
|
};
|
|
258
407
|
Schema.Union(StopSupervision, ContinueSupervision);
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/internal/supervisor-body.executor.ts
|
|
410
|
+
var SupervisorBodyExecutorDeps = class extends Context.Tag("SupervisorBodyExecutorDeps")() {};
|
|
259
411
|
const handleExhausted = (ctx, cause) => Effect.gen(function* () {
|
|
260
412
|
yield* Effect.zipRight(ctx.health.healthy.close, Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", ctx.name), "latch", "healthy"), 0));
|
|
261
413
|
yield* ctx.reportExhausted(cause);
|
|
@@ -272,7 +424,7 @@ const runSupervisionEpochWithBackoff = (attempt, ctx) => Effect.gen(function* ()
|
|
|
272
424
|
const loop = () => Effect.gen(function* () {
|
|
273
425
|
const step = yield* attempt.pipe(Effect.scoped);
|
|
274
426
|
return yield* Match.value(step).pipe(Match.tag("StopEpoch", () => Effect.succeed(new StopSupervision())), Match.tag("CooldownEpoch", () => Effect.gen(function* () {
|
|
275
|
-
yield* Effect.sleep(
|
|
427
|
+
yield* Effect.sleep(ctx.policy.cooldown);
|
|
276
428
|
yield* reopenHealthyAfterCooldown(ctx);
|
|
277
429
|
return new ContinueSupervision();
|
|
278
430
|
})), Match.tag("RestartEpoch", () => Effect.gen(function* () {
|
|
@@ -283,13 +435,18 @@ const runSupervisionEpochWithBackoff = (attempt, ctx) => Effect.gen(function* ()
|
|
|
283
435
|
});
|
|
284
436
|
return yield* loop();
|
|
285
437
|
});
|
|
438
|
+
const openAllReady = (ctx) => Effect.gen(function* () {
|
|
439
|
+
yield* Effect.yieldNow();
|
|
440
|
+
yield* Effect.forEach(ctx.booted, (b) => b.health.ready.await, { concurrency: "unbounded" });
|
|
441
|
+
yield* Effect.zipRight(ctx.health.ready.open, Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", ctx.name), "latch", "ready"), 1));
|
|
442
|
+
});
|
|
286
443
|
const superviseChild = (ctx, child, idx) => Effect.gen(function* () {
|
|
287
444
|
const childIntensityOpt = yield* Option.match(Option.fromNullable(child.childPolicy.intensity), {
|
|
288
445
|
onNone: () => Effect.succeed(Option.none()),
|
|
289
446
|
onSome: (cfg) => Effect.map(make(new BoundedIntensity(cfg)), Option.some)
|
|
290
447
|
});
|
|
291
448
|
const loop = () => Effect.gen(function* () {
|
|
292
|
-
const supIntensity = yield*
|
|
449
|
+
const supIntensity = yield* ctx.intensityEff;
|
|
293
450
|
const attempt = Effect.gen(function* () {
|
|
294
451
|
yield* ctx.health.paused.await;
|
|
295
452
|
const fiber = yield* Effect.forkScoped(child.run);
|
|
@@ -311,7 +468,10 @@ const superviseChild = (ctx, child, idx) => Effect.gen(function* () {
|
|
|
311
468
|
failedIndex: idx,
|
|
312
469
|
totalChildren: ctx.booted.length
|
|
313
470
|
});
|
|
314
|
-
return yield*
|
|
471
|
+
return yield* Either.match(decision, {
|
|
472
|
+
onLeft: () => handleExhausted(ctx, exit.cause),
|
|
473
|
+
onRight: (right) => Match.value(right).pipe(Match.tag("Continue", () => Effect.succeed(new StopEpoch())), Match.tag("Restart", () => handleRestart(ctx, exit.cause, Effect.void)), Match.exhaustive)
|
|
474
|
+
});
|
|
315
475
|
}
|
|
316
476
|
return new StopEpoch();
|
|
317
477
|
});
|
|
@@ -328,7 +488,7 @@ const runIndependent = (ctx) => Effect.gen(function* () {
|
|
|
328
488
|
});
|
|
329
489
|
const runGroup = (strategy, ctx) => Effect.gen(function* () {
|
|
330
490
|
const loop = () => Effect.gen(function* () {
|
|
331
|
-
const intensity = yield*
|
|
491
|
+
const intensity = yield* ctx.intensityEff;
|
|
332
492
|
const childIntensityTrackers = yield* Effect.forEach(ctx.booted, (b) => Option.match(Option.fromNullable(b.childPolicy.intensity), {
|
|
333
493
|
onNone: () => Effect.succeed(Option.none()),
|
|
334
494
|
onSome: (cfg) => Effect.map(make(new BoundedIntensity(cfg)), Option.some)
|
|
@@ -343,7 +503,7 @@ const runGroup = (strategy, ctx) => Effect.gen(function* () {
|
|
|
343
503
|
yield* Effect.forkScoped(openAllReady(ctx));
|
|
344
504
|
const [failedOffset, firstExit] = yield* raceForExit(fibers);
|
|
345
505
|
if (!Exit.isSuccess(firstExit)) {
|
|
346
|
-
const failedIdx =
|
|
506
|
+
const failedIdx = startIdx + failedOffset;
|
|
347
507
|
const failedBootedOpt = Option.fromNullable(ctx.booted[failedIdx]);
|
|
348
508
|
if (Option.isNone(failedBootedOpt)) return new StopEpoch();
|
|
349
509
|
if (failedBootedOpt.value.childPolicy.restart === "temporary") return new StopEpoch();
|
|
@@ -360,10 +520,13 @@ const runGroup = (strategy, ctx) => Effect.gen(function* () {
|
|
|
360
520
|
strategy,
|
|
361
521
|
exitSuccess: false,
|
|
362
522
|
intensityExceeded: yield* intensity.isExceeded,
|
|
363
|
-
|
|
364
|
-
|
|
523
|
+
totalChildren: ctx.booted.length,
|
|
524
|
+
failedIndex: failedIdx
|
|
525
|
+
});
|
|
526
|
+
return yield* Either.match(decision, {
|
|
527
|
+
onLeft: () => handleExhausted(ctx, firstExit.cause),
|
|
528
|
+
onRight: (right) => Match.value(right).pipe(Match.tag("Continue", () => Effect.succeed(new StopEpoch())), Match.tag("Restart", (restartDecision) => handleRestart(ctx, firstExit.cause, Ref.set(cursor, restartDecision.indices[0]))), Match.exhaustive)
|
|
365
529
|
});
|
|
366
|
-
return yield* Match.value(decision).pipe(Match.tag("Continue", () => Effect.succeed(new StopEpoch())), Match.tag("Exhausted", () => handleExhausted(ctx, firstExit.cause)), Match.tag("Restart", (restartDecision) => handleRestart(ctx, firstExit.cause, Ref.set(cursor, restartDecision.indices[0]))), Match.exhaustive);
|
|
367
530
|
}
|
|
368
531
|
return new StopEpoch();
|
|
369
532
|
});
|
|
@@ -373,13 +536,12 @@ const runGroup = (strategy, ctx) => Effect.gen(function* () {
|
|
|
373
536
|
return yield* loop();
|
|
374
537
|
});
|
|
375
538
|
const superviseTree = (strategy, ctx) => Match.value(strategy).pipe(Match.when("one_for_one", () => runIndependent(ctx)), Match.when("one_for_all", () => runGroup("one_for_all", ctx)), Match.when("rest_for_one", () => runGroup("rest_for_one", ctx)), Match.exhaustive);
|
|
376
|
-
//#endregion
|
|
377
|
-
//#region src/internal/supervisor-runtime.ts
|
|
378
539
|
const buildSupervisorBody = (sup, health, booted) => Effect.gen(function* () {
|
|
379
540
|
const policy = yield* sup.supervision;
|
|
380
|
-
const
|
|
541
|
+
const tracker = make(policy.intensity).pipe(Effect.runSync);
|
|
542
|
+
const intensityEff = Effect.succeed(tracker);
|
|
381
543
|
const reportRestart = (cause) => Effect.gen(function* () {
|
|
382
|
-
const reporter = yield*
|
|
544
|
+
const reporter = yield* SupervisorBodyExecutorDeps;
|
|
383
545
|
yield* Metric.increment(Metric.tagged(supervisorRestartsCounter, "supervisor", sup.name));
|
|
384
546
|
yield* reporter.onRestart(sup.name, cause);
|
|
385
547
|
yield* Option.match(Option.fromNullable(sup.reporter.onRestart), {
|
|
@@ -388,7 +550,7 @@ const buildSupervisorBody = (sup, health, booted) => Effect.gen(function* () {
|
|
|
388
550
|
});
|
|
389
551
|
});
|
|
390
552
|
const reportExhausted = (cause) => Effect.gen(function* () {
|
|
391
|
-
const reporter = yield*
|
|
553
|
+
const reporter = yield* SupervisorBodyExecutorDeps;
|
|
392
554
|
yield* Metric.increment(Metric.tagged(supervisorExhaustionsCounter, "supervisor", sup.name));
|
|
393
555
|
yield* reporter.onExhausted(sup.name, cause);
|
|
394
556
|
yield* Option.match(Option.fromNullable(sup.reporter.onExhausted), {
|
|
@@ -407,143 +569,55 @@ const buildSupervisorBody = (sup, health, booted) => Effect.gen(function* () {
|
|
|
407
569
|
});
|
|
408
570
|
yield* Effect.andThen(health.paused.await, runStrategy);
|
|
409
571
|
});
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (typeof spanAttributes === "undefined") return effect;
|
|
416
|
-
return Effect.tap(effect, () => Effect.orElse(spanAttributes, () => Effect.succeed({})).pipe(Effect.flatMap(Effect.annotateCurrentSpan)));
|
|
417
|
-
};
|
|
418
|
-
};
|
|
419
|
-
const applyTrackDuration = (hooks) => {
|
|
420
|
-
const { trackDuration } = hooks;
|
|
421
|
-
return (effect) => {
|
|
422
|
-
if (typeof trackDuration === "undefined") return effect;
|
|
423
|
-
return Metric.trackDuration(effect, trackDuration);
|
|
424
|
-
};
|
|
425
|
-
};
|
|
426
|
-
const applyTimeout = (config) => {
|
|
427
|
-
return (effect) => Effect.timeoutFail(effect, {
|
|
428
|
-
duration: config.tickTimeout,
|
|
429
|
-
onTimeout: () => new Cause.TimeoutException()
|
|
430
|
-
});
|
|
431
|
-
};
|
|
432
|
-
const applyInnerRetry = (hooks) => {
|
|
433
|
-
const { innerRetry } = hooks;
|
|
434
|
-
return (effect) => {
|
|
435
|
-
if (typeof innerRetry === "undefined") return effect;
|
|
436
|
-
return Effect.retry(effect, innerRetry);
|
|
437
|
-
};
|
|
438
|
-
};
|
|
439
|
-
const buildPollTick = (worker, health, gate) => {
|
|
440
|
-
const { tick, tickHooks } = worker;
|
|
441
|
-
const spanName = tick.spanName ?? "daemon.tick";
|
|
442
|
-
const withSpanAttrs = applySpanAttributes(tickHooks);
|
|
443
|
-
const withDuration = applyTrackDuration(tickHooks);
|
|
444
|
-
const withTimeout = applyTimeout(tick);
|
|
445
|
-
const withInnerRetry = applyInnerRetry(tickHooks);
|
|
446
|
-
const runWork = (work) => work.pipe(withSpanAttrs, withDuration).pipe(Effect.withSpan(spanName, {
|
|
447
|
-
root: true,
|
|
448
|
-
attributes: { "daemon.name": worker.name }
|
|
449
|
-
}), Effect.withLogSpan(spanName));
|
|
450
|
-
return withInnerRetry(withTimeout(Effect.andThen(health.paused.await, gate).pipe(Effect.flatMap(Option.match({
|
|
451
|
-
onNone: () => Effect.void,
|
|
452
|
-
onSome: runWork
|
|
453
|
-
}))))).pipe(Effect.tap(() => Effect.zipRight(health.ready.open, Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", health.name), "latch", "ready"), 1))), Effect.asVoid);
|
|
454
|
-
};
|
|
455
|
-
const wrapSpan = (worker, effect) => {
|
|
456
|
-
const spanName = worker.tick.spanName ?? "daemon.worker";
|
|
457
|
-
return effect.pipe(Effect.withSpan(spanName, {
|
|
458
|
-
root: true,
|
|
459
|
-
attributes: { "daemon.name": worker.name }
|
|
460
|
-
}), Effect.withLogSpan(spanName));
|
|
461
|
-
};
|
|
462
|
-
const buildPollLoop = (worker, loop, health) => {
|
|
463
|
-
const tick = buildPollTick(worker, health, loop.gate);
|
|
464
|
-
return Effect.repeat(tick, Schedule.spaced(loop.interval)).pipe(Effect.asVoid);
|
|
465
|
-
};
|
|
466
|
-
const buildStreamLoop = (worker, loop, health) => {
|
|
467
|
-
const body = Effect.gen(function* () {
|
|
468
|
-
yield* health.paused.await;
|
|
469
|
-
const fiber = yield* Effect.forkScoped(loop.stream.pipe(Stream.tap(() => Effect.zipRight(health.ready.open, Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", health.name), "latch", "ready"), 1))), Stream.runDrain));
|
|
470
|
-
const ready = applyTimeout(worker.tick)(health.ready.await);
|
|
471
|
-
yield* Effect.raceFirst(ready, Fiber.join(fiber));
|
|
472
|
-
yield* Fiber.join(fiber);
|
|
473
|
-
});
|
|
474
|
-
const retried = applyInnerRetry(worker.tickHooks)(body);
|
|
475
|
-
return wrapSpan(worker, retried.pipe(Effect.asVoid));
|
|
476
|
-
};
|
|
477
|
-
const buildSubscriptionLoop = (worker, loop, health) => {
|
|
478
|
-
const body = Effect.gen(function* () {
|
|
479
|
-
yield* health.paused.await;
|
|
480
|
-
yield* applyTimeout(worker.tick)(loop.acquire);
|
|
481
|
-
yield* Effect.zipRight(health.ready.open, Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", health.name), "latch", "ready"), 1));
|
|
482
|
-
return yield* Effect.never;
|
|
483
|
-
});
|
|
484
|
-
return wrapSpan(worker, body.pipe(Effect.asVoid));
|
|
485
|
-
};
|
|
486
|
-
const buildWorkerLoop = (worker, health) => Match.value(worker.loop).pipe(Match.tag("Poll", (loop) => buildPollLoop(worker, loop, health)), Match.tag("Stream", (loop) => buildStreamLoop(worker, loop, health)), Match.tag("Subscription", (loop) => buildSubscriptionLoop(worker, loop, health)), Match.exhaustive);
|
|
487
|
-
//#endregion
|
|
488
|
-
//#region src/internal/boot.ts
|
|
489
|
-
const allocateWorkerHealth = (name) => Effect.gen(function* () {
|
|
490
|
-
const ready = yield* Effect.makeLatch(false);
|
|
491
|
-
const healthy = yield* Effect.makeLatch(true);
|
|
492
|
-
const paused = yield* Effect.makeLatch(true);
|
|
493
|
-
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "ready"), 0);
|
|
494
|
-
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "healthy"), 1);
|
|
495
|
-
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "paused"), 1);
|
|
496
|
-
return {
|
|
497
|
-
name,
|
|
498
|
-
ready,
|
|
499
|
-
healthy,
|
|
500
|
-
paused
|
|
501
|
-
};
|
|
502
|
-
});
|
|
503
|
-
const allocateSupervisorHealth = (name, children) => Effect.gen(function* () {
|
|
504
|
-
const ready = yield* Effect.makeLatch(false);
|
|
505
|
-
const healthy = yield* Effect.makeLatch(true);
|
|
506
|
-
const paused = yield* Effect.makeLatch(true);
|
|
507
|
-
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "ready"), 0);
|
|
508
|
-
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "healthy"), 1);
|
|
509
|
-
yield* Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", name), "latch", "paused"), 1);
|
|
510
|
-
return {
|
|
511
|
-
name,
|
|
512
|
-
ready,
|
|
513
|
-
healthy,
|
|
514
|
-
paused,
|
|
515
|
-
children
|
|
516
|
-
};
|
|
517
|
-
});
|
|
518
|
-
const bootChild = (child) => {
|
|
519
|
-
if (isWorker(child)) return allocateWorkerHealth(child.name).pipe(Effect.map((health) => ({
|
|
520
|
-
name: child.name,
|
|
521
|
-
health,
|
|
522
|
-
run: buildWorkerLoop(child, health).pipe(Effect.orDie),
|
|
523
|
-
childPolicy: child.child
|
|
524
|
-
})));
|
|
525
|
-
return Effect.gen(function* () {
|
|
526
|
-
const bootedChildren = yield* Effect.forEach(child.children, bootChild);
|
|
527
|
-
const health = yield* allocateSupervisorHealth(child.name, bootedChildren.map((b) => b.health));
|
|
528
|
-
const run = buildSupervisorBody(child, health, bootedChildren).pipe(Effect.orDie);
|
|
572
|
+
const isWorker = (x) => WorkerTypeId in x;
|
|
573
|
+
const bootChild = (child) => Effect.gen(function* () {
|
|
574
|
+
if (isWorker(child)) {
|
|
575
|
+
const health = yield* allocateWorkerHealth(child.name);
|
|
576
|
+
const loop = buildWorkerLoop(child, health, healthStateGauge).pipe(Effect.orDie);
|
|
529
577
|
return {
|
|
530
578
|
name: child.name,
|
|
531
579
|
health,
|
|
532
|
-
run,
|
|
533
|
-
childPolicy:
|
|
580
|
+
run: loop,
|
|
581
|
+
childPolicy: child.child
|
|
534
582
|
};
|
|
583
|
+
}
|
|
584
|
+
const bootedChildren = yield* Effect.forEach(child.children, bootChild);
|
|
585
|
+
const health = yield* allocateSupervisorHealth(child.name, bootedChildren.map((b) => b.health));
|
|
586
|
+
const body = buildSupervisorBody(child, health, bootedChildren).pipe(Effect.orDie);
|
|
587
|
+
return {
|
|
588
|
+
name: child.name,
|
|
589
|
+
health,
|
|
590
|
+
run: body,
|
|
591
|
+
childPolicy: {}
|
|
592
|
+
};
|
|
593
|
+
});
|
|
594
|
+
const supervisor = (s) => Effect.gen(function* () {
|
|
595
|
+
const booted = yield* Effect.forEach(s.children, bootChild);
|
|
596
|
+
const health = yield* allocateSupervisorHealth(s.name, booted.map((b) => b.health));
|
|
597
|
+
const body = buildSupervisorBody(s, health, booted).pipe(Effect.orDie);
|
|
598
|
+
let locked;
|
|
599
|
+
if (isModeNone(s.lock)) locked = body;
|
|
600
|
+
else if (s.lock.mode === "required") locked = withLeaderLock(body, {
|
|
601
|
+
key: s.lock.key,
|
|
602
|
+
mode: "required",
|
|
603
|
+
acquireRetryBackoff: s.lock.acquireRetryBackoff
|
|
535
604
|
});
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
605
|
+
else locked = withLeaderLock(body, {
|
|
606
|
+
key: s.lock.key,
|
|
607
|
+
mode: "optional"
|
|
608
|
+
});
|
|
609
|
+
yield* Effect.forkScoped(locked.pipe(Effect.orDie));
|
|
610
|
+
return health;
|
|
611
|
+
});
|
|
612
|
+
Context.Tag("BuildDynamicExecutorDeps")();
|
|
539
613
|
const buildDynamic = (spec, health) => Effect.gen(function* () {
|
|
540
614
|
const state = yield* Ref.make({
|
|
541
615
|
nextId: 0,
|
|
542
616
|
children: HashMap.empty()
|
|
543
617
|
});
|
|
544
618
|
const startChildImpl = (args) => Effect.gen(function* () {
|
|
545
|
-
const
|
|
546
|
-
const loop = buildWorkerLoop(
|
|
619
|
+
const childWorker = spec.child(args);
|
|
620
|
+
const loop = buildWorkerLoop(childWorker, yield* allocateWorkerHealth(childWorker.name), healthStateGauge).pipe(Effect.orDie);
|
|
547
621
|
const removed = yield* Effect.makeLatch(false);
|
|
548
622
|
const reservedId = yield* Ref.modify(state, (current) => {
|
|
549
623
|
if (HashMap.size(current.children) >= spec.maxChildren) return [Option.none(), current];
|
|
@@ -620,69 +694,26 @@ const buildDynamic = (spec, health) => Effect.gen(function* () {
|
|
|
620
694
|
count: countImpl
|
|
621
695
|
};
|
|
622
696
|
});
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (lock.mode === "none") return Option.none();
|
|
627
|
-
return Option.some({
|
|
628
|
-
key: lock.key,
|
|
629
|
-
mode: lock.mode
|
|
630
|
-
});
|
|
631
|
-
};
|
|
632
|
-
//#endregion
|
|
633
|
-
//#region src/run.ts
|
|
634
|
-
const applyLock = (lock, effect) => {
|
|
635
|
-
const gate = decideLockGate(lock);
|
|
636
|
-
if (Option.isNone(gate)) return effect;
|
|
637
|
-
const locked = withLeaderLock(effect, gate.value);
|
|
638
|
-
if (lock.mode !== "required") return locked;
|
|
639
|
-
const retryWithRestart = (eff) => Effect.retry(eff, {
|
|
640
|
-
schedule: lock.acquireRetryBackoff,
|
|
641
|
-
while: Predicate.isTagged("LeaderLockNotAcquired")
|
|
642
|
-
}).pipe(Effect.catchTag("LeaderLockNotAcquired", () => retryWithRestart(eff)));
|
|
643
|
-
return retryWithRestart(locked);
|
|
644
|
-
};
|
|
645
|
-
const isModeNone = (lock) => lock.mode === "none";
|
|
646
|
-
function worker(w) {
|
|
647
|
-
return Effect.gen(function* () {
|
|
648
|
-
const health = yield* allocateWorkerHealth(w.name);
|
|
649
|
-
const loop = buildWorkerLoop(w, health).pipe(Effect.orDie);
|
|
650
|
-
if (isModeNone(w.lock)) yield* Effect.forkScoped(loop);
|
|
651
|
-
else {
|
|
652
|
-
const locked = applyLock(w.lock, loop);
|
|
653
|
-
yield* Effect.forkScoped(locked.pipe(Effect.orDie));
|
|
654
|
-
}
|
|
655
|
-
return health;
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
function supervisor(s) {
|
|
659
|
-
return Effect.gen(function* () {
|
|
660
|
-
const booted = yield* Effect.forEach(s.children, bootChild);
|
|
661
|
-
const health = yield* allocateSupervisorHealth(s.name, booted.map((b) => b.health));
|
|
662
|
-
const body = buildSupervisorBody(s, health, booted).pipe(Effect.orDie);
|
|
663
|
-
if (isModeNone(s.lock)) yield* Effect.forkScoped(body);
|
|
664
|
-
else {
|
|
665
|
-
const locked = applyLock(s.lock, body);
|
|
666
|
-
yield* Effect.forkScoped(locked.pipe(Effect.orDie));
|
|
667
|
-
}
|
|
668
|
-
return health;
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
const dynamic$1 = (spec) => Effect.gen(function* () {
|
|
672
|
-
return yield* buildDynamic(spec, yield* allocateSupervisorHealth(spec.name, []));
|
|
697
|
+
const dynamic$2 = (spec) => Effect.gen(function* () {
|
|
698
|
+
const health = yield* allocateSupervisorHealth(spec.name, []);
|
|
699
|
+
return yield* buildDynamic(spec, health);
|
|
673
700
|
});
|
|
674
|
-
const run = {
|
|
675
|
-
worker,
|
|
676
|
-
supervisor,
|
|
677
|
-
dynamic: dynamic$1
|
|
678
|
-
};
|
|
679
701
|
//#endregion
|
|
680
|
-
//#region src/supervision-
|
|
702
|
+
//#region src/internal/supervision-leader.state.ts
|
|
681
703
|
var LeaderConfig = class extends Context.Reference()("@systemfsoftware/effect-daemon-spec/LeaderConfig", { defaultValue: () => ({
|
|
682
704
|
backoffBase: Duration.seconds(1),
|
|
683
705
|
intensity: new UnboundedIntensity(),
|
|
684
706
|
cooldown: Duration.zero
|
|
685
707
|
}) }) {};
|
|
708
|
+
//#endregion
|
|
709
|
+
//#region src/internal/supervision-task.state.ts
|
|
710
|
+
var TaskConfig = class extends Context.Reference()("@systemfsoftware/effect-daemon-spec/TaskConfig", { defaultValue: () => ({
|
|
711
|
+
backoffBase: Duration.seconds(1),
|
|
712
|
+
intensity: new UnboundedIntensity(),
|
|
713
|
+
cooldown: Duration.zero
|
|
714
|
+
}) }) {};
|
|
715
|
+
//#endregion
|
|
716
|
+
//#region src/internal/supervision-worker.state.ts
|
|
686
717
|
var WorkerConfig = class extends Context.Reference()("@systemfsoftware/effect-daemon-spec/WorkerConfig", { defaultValue: () => ({
|
|
687
718
|
backoffBase: Duration.seconds(10),
|
|
688
719
|
intensity: new BoundedIntensity({
|
|
@@ -691,57 +722,108 @@ var WorkerConfig = class extends Context.Reference()("@systemfsoftware/effect-da
|
|
|
691
722
|
}),
|
|
692
723
|
cooldown: Duration.seconds(30)
|
|
693
724
|
}) }) {};
|
|
694
|
-
var TaskConfig = class extends Context.Reference()("@systemfsoftware/effect-daemon-spec/TaskConfig", { defaultValue: () => ({
|
|
695
|
-
backoffBase: Duration.seconds(1),
|
|
696
|
-
intensity: new UnboundedIntensity(),
|
|
697
|
-
cooldown: Duration.zero
|
|
698
|
-
}) }) {};
|
|
699
|
-
const Supervision = {
|
|
700
|
-
leader: (cap) => Effect.gen(function* () {
|
|
701
|
-
const config = yield* LeaderConfig;
|
|
702
|
-
return {
|
|
703
|
-
intensity: config.intensity,
|
|
704
|
-
backoff: cappedBackoff(config.backoffBase, cap),
|
|
705
|
-
cooldown: config.cooldown
|
|
706
|
-
};
|
|
707
|
-
}),
|
|
708
|
-
worker: (cap) => Effect.gen(function* () {
|
|
709
|
-
const config = yield* WorkerConfig;
|
|
710
|
-
return {
|
|
711
|
-
intensity: config.intensity,
|
|
712
|
-
backoff: cappedBackoff(config.backoffBase, cap),
|
|
713
|
-
cooldown: config.cooldown
|
|
714
|
-
};
|
|
715
|
-
}),
|
|
716
|
-
task: (budget) => Effect.gen(function* () {
|
|
717
|
-
const config = yield* TaskConfig;
|
|
718
|
-
return {
|
|
719
|
-
intensity: config.intensity,
|
|
720
|
-
backoff: Schedule.exponential(config.backoffBase).pipe(Schedule.jittered, Schedule.upTo(budget)),
|
|
721
|
-
cooldown: config.cooldown
|
|
722
|
-
};
|
|
723
|
-
}),
|
|
724
|
-
custom: (policy) => Effect.succeed(policy)
|
|
725
|
-
};
|
|
726
725
|
//#endregion
|
|
727
|
-
//#region src/
|
|
728
|
-
const
|
|
726
|
+
//#region src/supervision-custom.kernel.ts
|
|
727
|
+
const custom = (policy) => Effect.succeed(policy);
|
|
728
|
+
//#endregion
|
|
729
|
+
//#region src/supervision-leader.kernel.ts
|
|
730
|
+
const leader$1 = (config, cap) => Effect.succeed({
|
|
731
|
+
intensity: config.intensity,
|
|
732
|
+
backoff: cappedBackoff(config.backoffBase, cap),
|
|
733
|
+
cooldown: config.cooldown
|
|
734
|
+
});
|
|
735
|
+
//#endregion
|
|
736
|
+
//#region src/supervision-task.kernel.ts
|
|
737
|
+
const task$1 = (config, budget) => Effect.succeed({
|
|
738
|
+
intensity: config.intensity,
|
|
739
|
+
backoff: Schedule.exponential(config.backoffBase).pipe(Schedule.jittered, Schedule.upTo(budget)),
|
|
740
|
+
cooldown: config.cooldown
|
|
741
|
+
});
|
|
742
|
+
//#endregion
|
|
743
|
+
//#region src/supervision-worker.kernel.ts
|
|
744
|
+
const worker$1 = (config, cap) => Effect.succeed({
|
|
745
|
+
intensity: config.intensity,
|
|
746
|
+
backoff: cappedBackoff(config.backoffBase, cap),
|
|
747
|
+
cooldown: config.cooldown
|
|
748
|
+
});
|
|
749
|
+
//#endregion
|
|
750
|
+
//#region src/supervisor-dynamic.kernel.ts
|
|
751
|
+
const dynamic$1 = (opts) => ({
|
|
752
|
+
[DynamicSpecTypeId]: DynamicSpecTypeId,
|
|
753
|
+
name: opts.name,
|
|
754
|
+
child: opts.child,
|
|
755
|
+
maxChildren: opts.maxChildren ?? 1e3
|
|
756
|
+
});
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/supervisor-one-for-all.kernel.ts
|
|
759
|
+
const oneForAll$1 = (opts) => ({
|
|
729
760
|
[SupervisorTypeId]: SupervisorTypeId,
|
|
730
761
|
name: opts.name,
|
|
731
|
-
strategy,
|
|
762
|
+
strategy: "one_for_all",
|
|
732
763
|
children: opts.children,
|
|
733
764
|
supervision: opts.supervision,
|
|
734
765
|
lock: opts.lock,
|
|
735
766
|
reporter: opts.reporter ?? {}
|
|
736
767
|
});
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
[DynamicSpecTypeId]: DynamicSpecTypeId,
|
|
768
|
+
//#endregion
|
|
769
|
+
//#region src/supervisor-one-for-one.kernel.ts
|
|
770
|
+
const oneForOne$1 = (opts) => ({
|
|
771
|
+
[SupervisorTypeId]: SupervisorTypeId,
|
|
742
772
|
name: opts.name,
|
|
743
|
-
|
|
744
|
-
|
|
773
|
+
strategy: "one_for_one",
|
|
774
|
+
children: opts.children,
|
|
775
|
+
supervision: opts.supervision,
|
|
776
|
+
lock: opts.lock,
|
|
777
|
+
reporter: opts.reporter ?? {}
|
|
778
|
+
});
|
|
779
|
+
//#endregion
|
|
780
|
+
//#region src/supervisor-rest-for-one.kernel.ts
|
|
781
|
+
const restForOne$1 = (opts) => ({
|
|
782
|
+
[SupervisorTypeId]: SupervisorTypeId,
|
|
783
|
+
name: opts.name,
|
|
784
|
+
strategy: "rest_for_one",
|
|
785
|
+
children: opts.children,
|
|
786
|
+
supervision: opts.supervision,
|
|
787
|
+
lock: opts.lock,
|
|
788
|
+
reporter: opts.reporter ?? {}
|
|
745
789
|
});
|
|
746
790
|
//#endregion
|
|
747
|
-
|
|
791
|
+
//#region src/mod.ts
|
|
792
|
+
const poll = (opts) => poll$1(opts);
|
|
793
|
+
const stream = (opts) => stream$1(opts);
|
|
794
|
+
const subscription = (opts) => subscription$1(opts);
|
|
795
|
+
const Daemon = {
|
|
796
|
+
poll,
|
|
797
|
+
stream,
|
|
798
|
+
subscription
|
|
799
|
+
};
|
|
800
|
+
const run = {
|
|
801
|
+
worker,
|
|
802
|
+
supervisor,
|
|
803
|
+
dynamic: dynamic$2
|
|
804
|
+
};
|
|
805
|
+
const leader = (cap) => Effect.flatMap(LeaderConfig, (config) => leader$1(config, cap));
|
|
806
|
+
const task = (budget) => Effect.flatMap(TaskConfig, (config) => task$1(config, budget));
|
|
807
|
+
const supervision = (cap) => Effect.flatMap(WorkerConfig, (config) => worker$1(config, cap));
|
|
808
|
+
const Supervision = {
|
|
809
|
+
leader,
|
|
810
|
+
worker: supervision,
|
|
811
|
+
task,
|
|
812
|
+
custom
|
|
813
|
+
};
|
|
814
|
+
const dynamic = (opts) => dynamic$1(opts);
|
|
815
|
+
const oneForAll = (opts) => oneForAll$1(opts);
|
|
816
|
+
const oneForOne = (opts) => oneForOne$1(opts);
|
|
817
|
+
const restForOne = (opts) => restForOne$1(opts);
|
|
818
|
+
const WithLeaderLockExecutorLive = Layer.effect(WithLeaderLockExecutorDeps, Effect.gen(function* () {
|
|
819
|
+
return { withLock: (yield* LeaderLock).withLock };
|
|
820
|
+
}));
|
|
821
|
+
const SupervisorBodyExecutorLive = Layer.effect(SupervisorBodyExecutorDeps, Effect.gen(function* () {
|
|
822
|
+
const reporter = yield* DaemonReporter;
|
|
823
|
+
return {
|
|
824
|
+
onRestart: reporter.onRestart,
|
|
825
|
+
onExhausted: reporter.onExhausted
|
|
826
|
+
};
|
|
827
|
+
}));
|
|
828
|
+
//#endregion
|
|
829
|
+
export { BoundedIntensity, ChildPolicyConfig, Daemon, DaemonReporter, DynamicLimitExceeded, DynamicSpecTypeId, Intensity, IntensityConfig, LeaderConfig, LeaderLock, LeaderLockFromPrimitive, LeaderLockInfraError, LeaderLockNotAcquired, LockPolicyConfig, LockPrimitive, LockPrimitiveError, Noop, Supervision, SupervisorBodyExecutorDeps, SupervisorBodyExecutorLive, SupervisorTypeId, TaskConfig, TickPolicyConfig, UnboundedIntensity, WithLeaderLockExecutorDeps, WithLeaderLockExecutorLive, WorkerConfig, WorkerTypeId, cappedBackoff, dynamic, healthStateGauge, leader, oneForAll, oneForOne, poll, restForOne, run, stream, subscription, supervision, supervisor, supervisorChildrenGauge, supervisorExhaustionsCounter, supervisorRestartsCounter, task, withLeaderLock, worker };
|