@systemfsoftware/effect-daemon-spec 0.7.2 → 2.0.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/CHANGELOG.md +189 -0
- package/LICENSE +203 -21
- package/dist/effect-daemon-spec.d.ts +105 -154
- package/dist/index.d.ts +131 -175
- package/dist/index.mjs +469 -339
- package/package.json +25 -24
package/dist/index.mjs
CHANGED
|
@@ -1,31 +1,43 @@
|
|
|
1
|
-
import { Array
|
|
2
|
-
import {
|
|
1
|
+
import { Array, Cause, Clock, Context, Duration, Effect, Equal, Exit, Fiber, HashMap, Latch, Layer, Match, Metric, Option, Predicate, Ref, Result, Schedule, Schema, Scope, Stream, pipe } from "effect";
|
|
2
|
+
import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
3
|
+
import * as Arr from "effect/Array";
|
|
3
4
|
import * as Match$1 from "effect/Match";
|
|
5
|
+
import * as Result$1 from "effect/Result";
|
|
4
6
|
import * as S from "effect/Schema";
|
|
5
|
-
//#region src/
|
|
7
|
+
//#region src/Backoff.ts
|
|
6
8
|
const cappedBackoff = (base, cap) => {
|
|
7
|
-
const ceiling = Duration.
|
|
8
|
-
return Schedule.exponential(base).pipe(Schedule.jittered, Schedule.modifyDelay((
|
|
9
|
+
const ceiling = Duration.fromInputUnsafe(cap);
|
|
10
|
+
return Schedule.exponential(base).pipe(Schedule.jittered, Schedule.modifyDelay(({ duration }) => Effect.succeed(Duration.min(duration, ceiling))));
|
|
9
11
|
};
|
|
10
12
|
//#endregion
|
|
11
|
-
//#region src/
|
|
13
|
+
//#region src/Brands.ts
|
|
12
14
|
const WorkerTypeId = Symbol.for("@systemfsoftware/effect-daemon/Worker");
|
|
13
15
|
const SupervisorTypeId = Symbol.for("@systemfsoftware/effect-daemon/Supervisor");
|
|
14
16
|
const DynamicSpecTypeId = Symbol.for("@systemfsoftware/effect-daemon/DynamicSpec");
|
|
15
17
|
//#endregion
|
|
16
|
-
//#region src/
|
|
17
|
-
var DynamicLimitExceeded = class extends Schema.TaggedError()("DynamicLimitExceeded", { limit: Schema.Int.pipe(Schema.
|
|
18
|
+
//#region src/DaemonHealth.schema.ts
|
|
19
|
+
var DynamicLimitExceeded = class extends Schema.TaggedError()("DynamicLimitExceeded", { limit: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))) }) {};
|
|
20
|
+
Schema.decodeUnknownExit(DynamicLimitExceeded);
|
|
18
21
|
//#endregion
|
|
19
|
-
//#region src/
|
|
22
|
+
//#region src/DaemonMetrics.ts
|
|
20
23
|
const supervisorRestartsCounter = Metric.counter("app.daemon.supervisor.restart", { description: "Daemon supervisor restart count" });
|
|
21
24
|
const supervisorExhaustionsCounter = Metric.counter("app.daemon.supervisor.exhaustion", { description: "Daemon supervisor exhaustion count" });
|
|
22
25
|
const supervisorChildrenGauge = Metric.gauge("app.daemon.supervisor.children", { description: "Daemon supervisor current child count (dynamic supervisors only)" });
|
|
23
26
|
const healthStateGauge = Metric.gauge("app.daemon.health.state", { description: "Daemon health latch open (1) or closed (0); tagged by daemon name and latch (ready | healthy | paused)" });
|
|
24
27
|
//#endregion
|
|
25
|
-
//#region src/
|
|
28
|
+
//#region src/SupervisorDynamic.ts
|
|
29
|
+
const MAX_CHILDREN_CEILING = 1e3;
|
|
30
|
+
const dynamic$2 = (opts) => ({
|
|
31
|
+
[DynamicSpecTypeId]: DynamicSpecTypeId,
|
|
32
|
+
name: opts.name,
|
|
33
|
+
child: opts.child,
|
|
34
|
+
maxChildren: opts.maxChildren
|
|
35
|
+
});
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/DaemonPolicy.schema.ts
|
|
26
38
|
const IntensityConfig = Schema.Struct({
|
|
27
|
-
restarts: Schema.Int.pipe(Schema.
|
|
28
|
-
window: Schema.
|
|
39
|
+
restarts: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
40
|
+
window: Schema.Duration
|
|
29
41
|
});
|
|
30
42
|
const IntensityTypeId = Symbol.for("@systemfsoftware/effect-daemon-spec/Intensity");
|
|
31
43
|
var BoundedIntensity = class extends Schema.TaggedClass()("Bounded", IntensityConfig.fields) {
|
|
@@ -34,33 +46,54 @@ var BoundedIntensity = class extends Schema.TaggedClass()("Bounded", IntensityCo
|
|
|
34
46
|
var UnboundedIntensity = class extends Schema.TaggedClass()("Unbounded", {}) {
|
|
35
47
|
[IntensityTypeId] = IntensityTypeId;
|
|
36
48
|
};
|
|
37
|
-
const Intensity = Schema.Union(BoundedIntensity, UnboundedIntensity);
|
|
49
|
+
const Intensity = Schema.Union([BoundedIntensity, UnboundedIntensity]);
|
|
38
50
|
var ChildPolicyConfig = class extends Schema.Class("ChildPolicyConfig")({
|
|
39
|
-
restart: Schema.optional(Schema.
|
|
51
|
+
restart: Schema.optional(Schema.Literals([
|
|
52
|
+
"permanent",
|
|
53
|
+
"transient",
|
|
54
|
+
"temporary"
|
|
55
|
+
])),
|
|
40
56
|
intensity: Schema.optional(IntensityConfig)
|
|
41
57
|
}) {};
|
|
42
|
-
Schema.Class("SupervisorPolicyConfig")({
|
|
58
|
+
var SupervisorPolicyConfig = class extends Schema.Class("SupervisorPolicyConfig")({
|
|
43
59
|
intensity: Schema.optional(IntensityConfig),
|
|
44
|
-
cooldown: Schema.optional(Schema.
|
|
45
|
-
});
|
|
60
|
+
cooldown: Schema.optional(Schema.Duration)
|
|
61
|
+
}) {};
|
|
46
62
|
var LockPolicyConfig = class extends Schema.Class("LockPolicyConfig")({
|
|
47
|
-
mode: Schema.optional(Schema.
|
|
63
|
+
mode: Schema.optional(Schema.Literals([
|
|
64
|
+
"none",
|
|
65
|
+
"required",
|
|
66
|
+
"optional"
|
|
67
|
+
])),
|
|
48
68
|
key: Schema.optional(Schema.String)
|
|
49
69
|
}) {};
|
|
50
70
|
var TickPolicyConfig = class extends Schema.Class("TickPolicyConfig")({
|
|
51
71
|
spanName: Schema.optional(Schema.String),
|
|
52
|
-
tickTimeout: Schema.
|
|
53
|
-
startLogLevel: Schema.optional(Schema.
|
|
72
|
+
tickTimeout: Schema.Duration,
|
|
73
|
+
startLogLevel: Schema.optional(Schema.Literals(["debug", "info"]))
|
|
54
74
|
}) {};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
const MaxChildren = Schema.Int.pipe(Schema.check(Schema.isBetween({
|
|
76
|
+
minimum: 1,
|
|
77
|
+
maximum: MAX_CHILDREN_CEILING
|
|
78
|
+
})), Schema.brand("MaxChildren"));
|
|
79
|
+
Schema.Int.pipe(Schema.check(Schema.makeFilter((children) => children < 1 || children > 1e3)));
|
|
80
|
+
Schema.Finite.pipe(Schema.check(Schema.isBetween({
|
|
81
|
+
minimum: 1,
|
|
82
|
+
maximum: MAX_CHILDREN_CEILING
|
|
83
|
+
})), Schema.check(Schema.makeFilter((children) => !Number.isInteger(children))));
|
|
84
|
+
Schema.decodeUnknownExit(ChildPolicyConfig);
|
|
85
|
+
Schema.decodeUnknownExit(LockPolicyConfig);
|
|
86
|
+
Schema.decodeUnknownExit(TickPolicyConfig);
|
|
87
|
+
Schema.decodeUnknownExit(SupervisorPolicyConfig);
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/DaemonReporterAdapter.ts
|
|
90
|
+
var DaemonReporter = class extends Context.Service()("@systemfsoftware/effect-daemon-spec/DaemonReporterAdapter/DaemonReporter") {};
|
|
58
91
|
const Noop = Layer.succeed(DaemonReporter, DaemonReporter.of({
|
|
59
92
|
onRestart: () => Effect.void,
|
|
60
93
|
onExhausted: () => Effect.void
|
|
61
94
|
}));
|
|
62
95
|
//#endregion
|
|
63
|
-
//#region src/
|
|
96
|
+
//#region src/DaemonPoll.ts
|
|
64
97
|
const poll$1 = (opts) => {
|
|
65
98
|
if (typeof opts.prereq === "undefined") {
|
|
66
99
|
const gate = Effect.succeed(Option.some(Effect.asVoid(opts.work)));
|
|
@@ -95,7 +128,7 @@ const poll$1 = (opts) => {
|
|
|
95
128
|
};
|
|
96
129
|
};
|
|
97
130
|
//#endregion
|
|
98
|
-
//#region src/
|
|
131
|
+
//#region src/DaemonStream.ts
|
|
99
132
|
const stream$1 = (opts) => ({
|
|
100
133
|
[WorkerTypeId]: WorkerTypeId,
|
|
101
134
|
name: opts.name,
|
|
@@ -109,7 +142,7 @@ const stream$1 = (opts) => ({
|
|
|
109
142
|
lock: opts.lock
|
|
110
143
|
});
|
|
111
144
|
//#endregion
|
|
112
|
-
//#region src/
|
|
145
|
+
//#region src/DaemonSubscription.ts
|
|
113
146
|
const subscription$1 = (opts) => ({
|
|
114
147
|
[WorkerTypeId]: WorkerTypeId,
|
|
115
148
|
name: opts.name,
|
|
@@ -123,29 +156,32 @@ const subscription$1 = (opts) => ({
|
|
|
123
156
|
lock: opts.lock
|
|
124
157
|
});
|
|
125
158
|
//#endregion
|
|
126
|
-
//#region src/
|
|
159
|
+
//#region src/LeaderLock.ts
|
|
160
|
+
const isModeNone = (lock) => lock.mode === "none";
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/LeaderLock.schema.ts
|
|
127
163
|
var LeaderLockNotAcquired = class extends Schema.TaggedError()("LeaderLockNotAcquired", { key: Schema.String }) {};
|
|
128
164
|
var LeaderLockInfraError = class extends Schema.TaggedError()("LeaderLockInfraError", {
|
|
129
165
|
key: Schema.String,
|
|
130
166
|
cause: Schema.Unknown
|
|
131
167
|
}) {};
|
|
132
168
|
//#endregion
|
|
133
|
-
//#region src/
|
|
169
|
+
//#region src/LockPrimitive.schema.ts
|
|
134
170
|
var LockPrimitiveError = class extends Schema.TaggedError()("LockPrimitiveError", {
|
|
135
171
|
key: Schema.String,
|
|
136
172
|
cause: Schema.Unknown
|
|
137
173
|
}) {};
|
|
138
174
|
//#endregion
|
|
139
|
-
//#region src/
|
|
140
|
-
var LeaderLock = class LeaderLock extends Context.
|
|
175
|
+
//#region src/LeaderLockAdapter.ts
|
|
176
|
+
var LeaderLock = class LeaderLock extends Context.Service()("@systemfsoftware/effect-daemon-spec/LeaderLockAdapter/LeaderLock") {
|
|
141
177
|
static Noop = Layer.succeed(LeaderLock, LeaderLock.of({ withLock: (_key, self) => Effect.map(self, Option.some) }));
|
|
142
178
|
};
|
|
143
|
-
var LockPrimitive = class extends Context.
|
|
179
|
+
var LockPrimitive = class extends Context.Service()("@systemfsoftware/effect-daemon-spec/LeaderLockAdapter/LockPrimitive") {};
|
|
144
180
|
const LeaderLockFromPrimitive = Layer.effect(LeaderLock, Effect.gen(function* () {
|
|
145
181
|
const primitive = yield* LockPrimitive;
|
|
146
182
|
return LeaderLock.of({ withLock: (key, self) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
147
183
|
const scope = yield* Scope.make();
|
|
148
|
-
if (!(yield* restore(primitive.tryAcquire(key)).pipe(Scope.
|
|
184
|
+
if (!(yield* restore(primitive.tryAcquire(key)).pipe(Scope.provide(scope), Effect.mapError((cause) => LeaderLockInfraError.make({
|
|
149
185
|
key,
|
|
150
186
|
cause
|
|
151
187
|
})), Effect.onError(() => Scope.close(scope, Exit.void))))) {
|
|
@@ -157,14 +193,23 @@ const LeaderLockFromPrimitive = Layer.effect(LeaderLock, Effect.gen(function* ()
|
|
|
157
193
|
})) });
|
|
158
194
|
}));
|
|
159
195
|
//#endregion
|
|
160
|
-
//#region src/internal/
|
|
196
|
+
//#region src/internal/AllocateWorkerHealth.ts
|
|
161
197
|
const allocateWorkerHealth = (name) => Effect.gen(function* () {
|
|
162
|
-
const ready = yield*
|
|
163
|
-
const healthy = yield*
|
|
164
|
-
const paused = yield*
|
|
165
|
-
yield* Metric.
|
|
166
|
-
|
|
167
|
-
|
|
198
|
+
const ready = yield* Latch.make(false);
|
|
199
|
+
const healthy = yield* Latch.make(true);
|
|
200
|
+
const paused = yield* Latch.make(true);
|
|
201
|
+
yield* Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
202
|
+
daemon: name,
|
|
203
|
+
latch: "ready"
|
|
204
|
+
}), 0);
|
|
205
|
+
yield* Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
206
|
+
daemon: name,
|
|
207
|
+
latch: "healthy"
|
|
208
|
+
}), 1);
|
|
209
|
+
yield* Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
210
|
+
daemon: name,
|
|
211
|
+
latch: "paused"
|
|
212
|
+
}), 1);
|
|
168
213
|
return {
|
|
169
214
|
name,
|
|
170
215
|
ready,
|
|
@@ -173,25 +218,22 @@ const allocateWorkerHealth = (name) => Effect.gen(function* () {
|
|
|
173
218
|
};
|
|
174
219
|
});
|
|
175
220
|
//#endregion
|
|
176
|
-
//#region src/internal/
|
|
221
|
+
//#region src/internal/BuildWorkerLoop.ts
|
|
177
222
|
const applySpanAttributes = (hooks) => {
|
|
178
223
|
const { spanAttributes } = hooks;
|
|
179
224
|
return (effect) => {
|
|
180
225
|
if (typeof spanAttributes === "undefined") return effect;
|
|
181
|
-
return Effect.tap(effect, () => Effect.
|
|
226
|
+
return Effect.tap(effect, () => Effect.orElseSucceed(spanAttributes, () => ({})).pipe(Effect.flatMap(Effect.annotateCurrentSpan)));
|
|
182
227
|
};
|
|
183
228
|
};
|
|
184
229
|
const applyTrackDuration = (hooks) => {
|
|
185
230
|
const { trackDuration } = hooks;
|
|
186
231
|
return (effect) => {
|
|
187
232
|
if (typeof trackDuration === "undefined") return effect;
|
|
188
|
-
return
|
|
233
|
+
return Effect.trackDuration(effect, trackDuration);
|
|
189
234
|
};
|
|
190
235
|
};
|
|
191
|
-
const applyTimeout = (timeout) => (effect) => Effect.
|
|
192
|
-
duration: timeout,
|
|
193
|
-
onTimeout: () => new Cause.TimeoutException()
|
|
194
|
-
});
|
|
236
|
+
const applyTimeout = (timeout) => (effect) => effect.pipe(Effect.timeout(timeout), Effect.catchTag("TimeoutError", () => Effect.fail(new Cause.TimeoutError())));
|
|
195
237
|
const applyInnerRetry = (hooks) => {
|
|
196
238
|
const { innerRetry } = hooks;
|
|
197
239
|
return (effect) => {
|
|
@@ -199,7 +241,10 @@ const applyInnerRetry = (hooks) => {
|
|
|
199
241
|
return Effect.retry(effect, innerRetry);
|
|
200
242
|
};
|
|
201
243
|
};
|
|
202
|
-
const openReadyGauge = (gauge, name) => Metric.
|
|
244
|
+
const openReadyGauge = (gauge, name) => Metric.update(Metric.withAttributes(gauge, {
|
|
245
|
+
daemon: name,
|
|
246
|
+
latch: "ready"
|
|
247
|
+
}), 1);
|
|
203
248
|
const buildPollTick = (worker, health, gate, readyGauge) => {
|
|
204
249
|
const spanName = worker.tick.spanName ?? "daemon.tick";
|
|
205
250
|
const withSpanAttrs = applySpanAttributes(worker.tickHooks);
|
|
@@ -213,7 +258,7 @@ const buildPollTick = (worker, health, gate, readyGauge) => {
|
|
|
213
258
|
return withInnerRetry(withTimeout(Effect.andThen(health.paused.await, gate).pipe(Effect.flatMap(Option.match({
|
|
214
259
|
onNone: () => Effect.void,
|
|
215
260
|
onSome: runWork
|
|
216
|
-
}))))).pipe(Effect.tap(() => Effect.
|
|
261
|
+
}))))).pipe(Effect.tap(() => Effect.andThen(health.ready.open, openReadyGauge(readyGauge, worker.name))), Effect.asVoid);
|
|
217
262
|
};
|
|
218
263
|
const wrapSpan = (worker, effect, spanName) => {
|
|
219
264
|
const finalSpan = spanName ?? "daemon.worker";
|
|
@@ -230,10 +275,10 @@ const propagateExit = (exit) => Match.value(exit).pipe(Match.tag("Failure", ({ c
|
|
|
230
275
|
const buildStreamLoop = (worker, loop, health, readyGauge) => {
|
|
231
276
|
const body = Effect.gen(function* () {
|
|
232
277
|
yield* health.paused.await;
|
|
233
|
-
const fiber = yield* Effect.forkScoped(loop.stream.pipe(Stream.tap(() => Effect.
|
|
278
|
+
const fiber = yield* Effect.forkScoped(loop.stream.pipe(Stream.tap(() => Effect.andThen(health.ready.open, openReadyGauge(readyGauge, worker.name))), Stream.runDrain), { startImmediately: true });
|
|
234
279
|
const ready = applyTimeout(worker.tick.tickTimeout)(health.ready.await);
|
|
235
|
-
yield* Effect.raceFirst(ready,
|
|
236
|
-
yield*
|
|
280
|
+
yield* Effect.raceFirst(ready, Fiber.await(fiber).pipe(Effect.flatMap(propagateExit)));
|
|
281
|
+
yield* Fiber.await(fiber).pipe(Effect.flatMap(propagateExit));
|
|
237
282
|
});
|
|
238
283
|
const retried = applyInnerRetry(worker.tickHooks)(body);
|
|
239
284
|
return wrapSpan(worker, retried.pipe(Effect.asVoid));
|
|
@@ -242,20 +287,19 @@ const buildSubscriptionLoop = (worker, loop, health, readyGauge) => {
|
|
|
242
287
|
const body = Effect.gen(function* () {
|
|
243
288
|
yield* health.paused.await;
|
|
244
289
|
yield* applyTimeout(worker.tick.tickTimeout)(loop.acquire);
|
|
245
|
-
yield* Effect.
|
|
290
|
+
yield* Effect.andThen(health.ready.open, openReadyGauge(readyGauge, worker.name));
|
|
246
291
|
return yield* Effect.never;
|
|
247
292
|
});
|
|
248
293
|
return wrapSpan(worker, body.pipe(Effect.asVoid));
|
|
249
294
|
};
|
|
250
295
|
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
296
|
//#endregion
|
|
252
|
-
//#region src/internal/
|
|
253
|
-
|
|
254
|
-
function withLeaderLock(self, options) {
|
|
297
|
+
//#region src/internal/WithLeaderLockExecutor.ts
|
|
298
|
+
function withLeaderLock(self, options, lock) {
|
|
255
299
|
const acquire = Effect.gen(function* () {
|
|
256
|
-
const out = yield*
|
|
300
|
+
const out = yield* lock.withLock(options.key, self);
|
|
257
301
|
if (Option.isSome(out)) return out.value;
|
|
258
|
-
return yield* Match.value(options.mode).pipe(Match.when("required", () => Effect.fail(
|
|
302
|
+
return yield* Match.value(options.mode).pipe(Match.when("required", () => Effect.fail(LeaderLockNotAcquired.make({ key: options.key }))), Match.when("optional", () => Effect.void), Match.exhaustive);
|
|
259
303
|
});
|
|
260
304
|
const retryOnce = (current) => Effect.retry(acquire, {
|
|
261
305
|
schedule: current,
|
|
@@ -266,37 +310,59 @@ function withLeaderLock(self, options) {
|
|
|
266
310
|
return acquire;
|
|
267
311
|
}
|
|
268
312
|
//#endregion
|
|
269
|
-
//#region src/
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
313
|
+
//#region src/internal/WithLockByModeExecutor.ts
|
|
314
|
+
/**
|
|
315
|
+
* Runs `self` under whichever lock discipline the binding asks for.
|
|
316
|
+
*
|
|
317
|
+
* An absent lock adapter and a `mode: 'none'` spec are one case, not two: a spec that
|
|
318
|
+
* declines the lock and a composition root that supplies no adapter both leave the
|
|
319
|
+
* body unwrapped, and nothing downstream can tell them apart from the effect that
|
|
320
|
+
* comes back. `unlocked` is that single case; `locked` dispatches on the spec's mode
|
|
321
|
+
* to the existing `withLeaderLock` calls. There is no null arm because the null state
|
|
322
|
+
* is now a constructor choice, not a runtime case.
|
|
323
|
+
*
|
|
324
|
+
* The worker and the supervisor make the same choice over the same cases, so it is
|
|
325
|
+
* made here once rather than in each of them.
|
|
326
|
+
*/
|
|
327
|
+
const withLockByMode = (self, binding) => {
|
|
328
|
+
if (binding.kind === "unlocked") return self;
|
|
329
|
+
if (binding.spec.mode === "required") return withLeaderLock(self, {
|
|
330
|
+
key: binding.spec.key,
|
|
281
331
|
mode: "required",
|
|
282
|
-
acquireRetryBackoff:
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
key:
|
|
332
|
+
acquireRetryBackoff: binding.spec.acquireRetryBackoff
|
|
333
|
+
}, binding.lock);
|
|
334
|
+
return withLeaderLock(self, {
|
|
335
|
+
key: binding.spec.key,
|
|
286
336
|
mode: "optional"
|
|
287
|
-
});
|
|
337
|
+
}, binding.lock);
|
|
338
|
+
};
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/DaemonWorkerExecutor.ts
|
|
341
|
+
const worker$2 = (w, binding) => Effect.gen(function* () {
|
|
342
|
+
const health = yield* allocateWorkerHealth(w.name);
|
|
343
|
+
const loop = buildWorkerLoop(w, health, healthStateGauge).pipe(Effect.orDie);
|
|
344
|
+
const locked = withLockByMode(loop, binding);
|
|
288
345
|
yield* Effect.forkScoped(locked.pipe(Effect.orDie));
|
|
289
346
|
return health;
|
|
290
347
|
});
|
|
291
348
|
//#endregion
|
|
292
|
-
//#region src/internal/
|
|
349
|
+
//#region src/internal/AllocateSupervisorHealth.ts
|
|
293
350
|
const allocateSupervisorHealth = (name, children) => Effect.gen(function* () {
|
|
294
|
-
const ready = yield*
|
|
295
|
-
const healthy = yield*
|
|
296
|
-
const paused = yield*
|
|
297
|
-
yield* Metric.
|
|
298
|
-
|
|
299
|
-
|
|
351
|
+
const ready = yield* Latch.make(false);
|
|
352
|
+
const healthy = yield* Latch.make(true);
|
|
353
|
+
const paused = yield* Latch.make(true);
|
|
354
|
+
yield* Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
355
|
+
daemon: name,
|
|
356
|
+
latch: "ready"
|
|
357
|
+
}), 0);
|
|
358
|
+
yield* Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
359
|
+
daemon: name,
|
|
360
|
+
latch: "healthy"
|
|
361
|
+
}), 1);
|
|
362
|
+
yield* Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
363
|
+
daemon: name,
|
|
364
|
+
latch: "paused"
|
|
365
|
+
}), 1);
|
|
300
366
|
return {
|
|
301
367
|
name,
|
|
302
368
|
ready,
|
|
@@ -306,20 +372,114 @@ const allocateSupervisorHealth = (name, children) => Effect.gen(function* () {
|
|
|
306
372
|
};
|
|
307
373
|
});
|
|
308
374
|
//#endregion
|
|
309
|
-
//#region src/internal/
|
|
375
|
+
//#region src/internal/BuildDynamicExecutor.ts
|
|
376
|
+
const buildDynamic = (spec, health) => Effect.gen(function* () {
|
|
377
|
+
const state = yield* Ref.make({
|
|
378
|
+
nextId: 0,
|
|
379
|
+
children: HashMap.empty()
|
|
380
|
+
});
|
|
381
|
+
const startChildImpl = (args) => Effect.gen(function* () {
|
|
382
|
+
const childWorker = spec.child(args);
|
|
383
|
+
const workerHealth = yield* allocateWorkerHealth(childWorker.name);
|
|
384
|
+
const loop = buildWorkerLoop(childWorker, workerHealth, healthStateGauge).pipe(Effect.orDie);
|
|
385
|
+
const removed = yield* Latch.make(false);
|
|
386
|
+
const reservedId = yield* Ref.modify(state, (current) => {
|
|
387
|
+
if (HashMap.size(current.children) >= spec.maxChildren) return [Option.none(), current];
|
|
388
|
+
const children = HashMap.set(current.children, current.nextId, Equal.byReferenceUnsafe({
|
|
389
|
+
fiber: Option.none(),
|
|
390
|
+
removed
|
|
391
|
+
}));
|
|
392
|
+
return [Option.some(current.nextId), {
|
|
393
|
+
nextId: current.nextId + 1,
|
|
394
|
+
children
|
|
395
|
+
}];
|
|
396
|
+
});
|
|
397
|
+
if (Option.isNone(reservedId)) return yield* DynamicLimitExceeded.make({ limit: spec.maxChildren });
|
|
398
|
+
const id = reservedId.value;
|
|
399
|
+
yield* Metric.update(supervisorChildrenGauge, HashMap.size(yield* Ref.get(state).pipe(Effect.map((s) => s.children))));
|
|
400
|
+
const cleanup = Effect.gen(function* () {
|
|
401
|
+
const count = yield* Ref.modify(state, (current) => {
|
|
402
|
+
const children = HashMap.remove(current.children, id);
|
|
403
|
+
return [HashMap.size(children), {
|
|
404
|
+
...current,
|
|
405
|
+
children
|
|
406
|
+
}];
|
|
407
|
+
});
|
|
408
|
+
yield* Metric.update(supervisorChildrenGauge, count);
|
|
409
|
+
yield* removed.open;
|
|
410
|
+
}).pipe(Effect.asVoid);
|
|
411
|
+
const fiber = yield* Effect.forkScoped(loop.pipe(Effect.ensuring(cleanup)));
|
|
412
|
+
const count = yield* Ref.modify(state, (current) => {
|
|
413
|
+
const childOpt = HashMap.get(current.children, id);
|
|
414
|
+
if (Option.isNone(childOpt)) return [HashMap.size(current.children), current];
|
|
415
|
+
const children = HashMap.set(current.children, id, Equal.byReferenceUnsafe({
|
|
416
|
+
...childOpt.value,
|
|
417
|
+
fiber: Option.some(fiber)
|
|
418
|
+
}));
|
|
419
|
+
return [HashMap.size(children), {
|
|
420
|
+
nextId: current.nextId,
|
|
421
|
+
children
|
|
422
|
+
}];
|
|
423
|
+
});
|
|
424
|
+
yield* Metric.update(supervisorChildrenGauge, count);
|
|
425
|
+
return {
|
|
426
|
+
id,
|
|
427
|
+
removed: removed.await
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
const stopChildImpl = (ref) => Effect.gen(function* () {
|
|
431
|
+
const [stateOpt, count] = yield* Ref.modify(state, (current) => {
|
|
432
|
+
const found = HashMap.get(current.children, ref.id);
|
|
433
|
+
const children = HashMap.remove(current.children, ref.id);
|
|
434
|
+
return [[found, HashMap.size(children)], {
|
|
435
|
+
...current,
|
|
436
|
+
children
|
|
437
|
+
}];
|
|
438
|
+
});
|
|
439
|
+
if (Option.isSome(stateOpt)) {
|
|
440
|
+
const { fiber, removed } = stateOpt.value;
|
|
441
|
+
yield* Option.match(fiber, {
|
|
442
|
+
onNone: () => Effect.void,
|
|
443
|
+
onSome: (running) => Effect.gen(function* () {
|
|
444
|
+
yield* Fiber.interrupt(running);
|
|
445
|
+
yield* Fiber.await(running);
|
|
446
|
+
})
|
|
447
|
+
});
|
|
448
|
+
yield* Metric.update(supervisorChildrenGauge, count);
|
|
449
|
+
yield* removed.open;
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
const countImpl = Ref.get(state).pipe(Effect.map((current) => HashMap.size(current.children)));
|
|
453
|
+
yield* Effect.andThen(health.ready.open, Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
454
|
+
daemon: spec.name,
|
|
455
|
+
latch: "ready"
|
|
456
|
+
}), 1));
|
|
457
|
+
return {
|
|
458
|
+
health,
|
|
459
|
+
startChild: startChildImpl,
|
|
460
|
+
stopChild: stopChildImpl,
|
|
461
|
+
count: countImpl
|
|
462
|
+
};
|
|
463
|
+
});
|
|
464
|
+
const dynamic$1 = (spec) => Effect.gen(function* () {
|
|
465
|
+
const health = yield* allocateSupervisorHealth(spec.name, []);
|
|
466
|
+
return yield* buildDynamic(spec, health);
|
|
467
|
+
});
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region src/internal/IntensityWindow.ts
|
|
310
470
|
const isWithinWindow = (now, windowMillis) => (t) => now - t <= windowMillis;
|
|
311
471
|
const keepWithin = (now, windowMillis) => (ts) => ts.filter(isWithinWindow(now, windowMillis));
|
|
312
472
|
const pruneTimestamps = (ts, now, windowMillis) => keepWithin(now, windowMillis)(ts);
|
|
313
473
|
const recordTimestamp = (ts, now, windowMillis) => [now, ...pruneTimestamps(ts, now, windowMillis)];
|
|
314
474
|
const exceedsRestarts = (count, restarts) => count > restarts;
|
|
315
475
|
//#endregion
|
|
316
|
-
//#region src/internal/
|
|
476
|
+
//#region src/internal/Intensity.ts
|
|
317
477
|
const neverExceeds = {
|
|
318
478
|
record: Effect.void,
|
|
319
479
|
isExceeded: Effect.succeed(false),
|
|
320
480
|
count: Effect.succeed(0)
|
|
321
481
|
};
|
|
322
|
-
const
|
|
482
|
+
const make = (restarts, window) => Effect.gen(function* () {
|
|
323
483
|
const windowMillis = Duration.toMillis(window);
|
|
324
484
|
const timestamps = yield* Ref.make([]);
|
|
325
485
|
const prune = (now) => Ref.modify(timestamps, (ts) => {
|
|
@@ -333,7 +493,8 @@ const boundedTracker = (restarts, window) => Effect.gen(function* () {
|
|
|
333
493
|
}),
|
|
334
494
|
isExceeded: Effect.gen(function* () {
|
|
335
495
|
const now = yield* Clock.currentTimeMillis;
|
|
336
|
-
|
|
496
|
+
const active = yield* prune(now);
|
|
497
|
+
return exceedsRestarts(active.length, restarts);
|
|
337
498
|
}),
|
|
338
499
|
count: Effect.gen(function* () {
|
|
339
500
|
const now = yield* Clock.currentTimeMillis;
|
|
@@ -341,34 +502,27 @@ const boundedTracker = (restarts, window) => Effect.gen(function* () {
|
|
|
341
502
|
})
|
|
342
503
|
};
|
|
343
504
|
});
|
|
344
|
-
const make = (intensity) => Match.value(intensity).pipe(Match.tag("Unbounded", () => Effect.succeed(neverExceeds)), Match.tag("Bounded", ({ restarts, window }) => boundedTracker(restarts, window)), Match.exhaustive);
|
|
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
505
|
//#endregion
|
|
349
|
-
//#region src/internal/
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}))) }));
|
|
370
|
-
//#endregion
|
|
371
|
-
//#region src/internal/restart-decision.workflow.ts
|
|
506
|
+
//#region src/internal/RaceForExit.ts
|
|
507
|
+
const raceForExit = (fibers) => Effect.raceAll(fibers.map((f, idx) => Fiber.await(f).pipe(Effect.map((exit) => [idx, exit]))));
|
|
508
|
+
//#endregion
|
|
509
|
+
//#region src/internal/RestartDecision.workflow.ts
|
|
510
|
+
/**
|
|
511
|
+
* The child indices a restart covers, by supervision strategy.
|
|
512
|
+
*
|
|
513
|
+
* A pure total function: the one part of the restart decision that is computation rather
|
|
514
|
+
* than dispatch, so it lives in the decision cell beside the `Workflow.make` it serves.
|
|
515
|
+
*/
|
|
516
|
+
const restartIndicesFor = (strategy, failedIndex, total) => Match$1.value(strategy).pipe(Match$1.when("one_for_one", () => [failedIndex]), Match$1.when("one_for_all", () => Arr.range(0, total - 1)), Match$1.when("rest_for_one", () => Arr.range(failedIndex, total - 1)), Match$1.exhaustive);
|
|
517
|
+
/**
|
|
518
|
+
* The cross-field invariant the decode input carries: a failed child's index addresses one of
|
|
519
|
+
* the children that exist.
|
|
520
|
+
*
|
|
521
|
+
* It lives here rather than inline in `Schema.filter` because naming it makes it reachable by
|
|
522
|
+
* a property test, which an inline arrow is not. The schema imports the name to build its
|
|
523
|
+
* filter; the decision file is where the invariant is owned.
|
|
524
|
+
*/
|
|
525
|
+
const failedIndexAddressesAChild = (input) => input.failedIndex < input.totalChildren;
|
|
372
526
|
const RestartDecisionTypeId = Symbol.for("@systemfsoftware/effect-daemon/RestartDecision");
|
|
373
527
|
var RestartDecisionContinue = class extends S.TaggedClass()("Continue", {}) {
|
|
374
528
|
[RestartDecisionTypeId] = RestartDecisionTypeId;
|
|
@@ -379,13 +533,32 @@ var RestartDecisionRestart = class extends S.TaggedClass()("Restart", { indices:
|
|
|
379
533
|
var RestartDecisionExhausted = class extends S.TaggedError()("Exhausted", {}) {
|
|
380
534
|
[RestartDecisionTypeId] = RestartDecisionTypeId;
|
|
381
535
|
};
|
|
382
|
-
const
|
|
383
|
-
const decideRestart = (input) => Match$1.value(input).pipe(Match$1.when({ exitSuccess: true }, () => right(new RestartDecisionContinue())), Match$1.when({
|
|
536
|
+
const decideRestart = Workflow.make((command) => Match$1.value(command).pipe(Match$1.when({ exitSuccess: true }, () => Result$1.succeed(RestartDecisionContinue.make())), Match$1.when({
|
|
384
537
|
exitSuccess: false,
|
|
385
538
|
intensityExceeded: true
|
|
386
|
-
}, () =>
|
|
539
|
+
}, () => Result$1.fail(RestartDecisionExhausted.make())), Match$1.orElse(() => Result$1.succeed(RestartDecisionRestart.make({ indices: restartIndicesFor(command.strategy, command.failedIndex, command.totalChildren) })))));
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/internal/RestartDecision.schema.ts
|
|
542
|
+
const RestartStrategy = Schema.Literals([
|
|
543
|
+
"one_for_one",
|
|
544
|
+
"one_for_all",
|
|
545
|
+
"rest_for_one"
|
|
546
|
+
]);
|
|
547
|
+
Schema.Struct({
|
|
548
|
+
strategy: RestartStrategy,
|
|
549
|
+
totalChildren: Schema.Int.pipe(Schema.check(Schema.isBetween({
|
|
550
|
+
minimum: 1,
|
|
551
|
+
maximum: MAX_CHILDREN_CEILING
|
|
552
|
+
}))),
|
|
553
|
+
failedIndex: Schema.Int.pipe(Schema.check(Schema.isBetween({
|
|
554
|
+
minimum: 0,
|
|
555
|
+
maximum: MAX_CHILDREN_CEILING
|
|
556
|
+
}))),
|
|
557
|
+
exitSuccess: Schema.Boolean,
|
|
558
|
+
intensityExceeded: Schema.Boolean
|
|
559
|
+
}).pipe(Schema.check(Schema.makeFilter(failedIndexAddressesAChild, { message: "failedIndex must be < totalChildren" })));
|
|
387
560
|
//#endregion
|
|
388
|
-
//#region src/internal/
|
|
561
|
+
//#region src/internal/SupervisionEpoch.schema.ts
|
|
389
562
|
const EpochStepTypeId = Symbol.for("@systemfsoftware/effect-daemon/EpochStep");
|
|
390
563
|
var StopEpoch = class extends Schema.TaggedClass()("StopEpoch", {}) {
|
|
391
564
|
[EpochStepTypeId] = EpochStepTypeId;
|
|
@@ -396,7 +569,11 @@ var RestartEpoch = class extends Schema.TaggedClass()("RestartEpoch", {}) {
|
|
|
396
569
|
var CooldownEpoch = class extends Schema.TaggedClass()("CooldownEpoch", {}) {
|
|
397
570
|
[EpochStepTypeId] = EpochStepTypeId;
|
|
398
571
|
};
|
|
399
|
-
Schema.Union(
|
|
572
|
+
Schema.Union([
|
|
573
|
+
StopEpoch,
|
|
574
|
+
RestartEpoch,
|
|
575
|
+
CooldownEpoch
|
|
576
|
+
]);
|
|
400
577
|
const SupervisionEpochResultTypeId = Symbol.for("@systemfsoftware/effect-daemon/SupervisionEpochResult");
|
|
401
578
|
var StopSupervision = class extends Schema.TaggedClass()("StopSupervision", {}) {
|
|
402
579
|
[SupervisionEpochResultTypeId] = SupervisionEpochResultTypeId;
|
|
@@ -404,76 +581,115 @@ var StopSupervision = class extends Schema.TaggedClass()("StopSupervision", {})
|
|
|
404
581
|
var ContinueSupervision = class extends Schema.TaggedClass()("ContinueSupervision", {}) {
|
|
405
582
|
[SupervisionEpochResultTypeId] = SupervisionEpochResultTypeId;
|
|
406
583
|
};
|
|
407
|
-
Schema.Union(StopSupervision, ContinueSupervision);
|
|
584
|
+
Schema.Union([StopSupervision, ContinueSupervision]);
|
|
408
585
|
//#endregion
|
|
409
|
-
//#region src/internal/
|
|
410
|
-
var SupervisorBodyExecutorDeps = class extends Context.Tag("@systemfsoftware/effect-daemon-spec/internal/supervisor-body.executor/SupervisorBodyExecutorDeps")() {};
|
|
586
|
+
//#region src/internal/SupervisorBodyExecutor.ts
|
|
411
587
|
const handleExhausted = (ctx, cause) => Effect.gen(function* () {
|
|
412
|
-
yield* Effect.
|
|
588
|
+
yield* Effect.andThen(ctx.health.healthy.close, Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
589
|
+
daemon: ctx.name,
|
|
590
|
+
latch: "healthy"
|
|
591
|
+
}), 0));
|
|
413
592
|
yield* ctx.reportExhausted(cause);
|
|
414
|
-
return
|
|
593
|
+
return CooldownEpoch.make();
|
|
415
594
|
});
|
|
416
595
|
const handleRestart = (ctx, cause, onSignal) => Effect.gen(function* () {
|
|
417
596
|
yield* ctx.reportRestart(cause);
|
|
418
597
|
yield* onSignal;
|
|
419
|
-
return
|
|
598
|
+
return RestartEpoch.make();
|
|
420
599
|
});
|
|
421
|
-
|
|
600
|
+
/**
|
|
601
|
+
* The restart decision, as a description whose phases chain by type and read in the order
|
|
602
|
+
* they run.
|
|
603
|
+
*
|
|
604
|
+
* The read is a bump-and-report: recording a restart is how the current rate is obtained, so
|
|
605
|
+
* the mutation is a product gathered across the read's interior rather than a write standing
|
|
606
|
+
* before a decision. That is what keeps this one layer instead of two, and it is why
|
|
607
|
+
* `intensity.record` sits where it does.
|
|
608
|
+
*
|
|
609
|
+
* `encode` is the identity because nothing needs shaping — the decision is already what the
|
|
610
|
+
* write consumes — and the write only dispatches over the tags the decision produced.
|
|
611
|
+
*
|
|
612
|
+
* A description is built per failure because the write needs that failure's context, and the
|
|
613
|
+
* phase signatures hand the command to the read alone. Restarts are rare, so the allocation is
|
|
614
|
+
* paid only when a supervised child has actually died.
|
|
615
|
+
*/
|
|
616
|
+
const restartDescription = (spec) => pipe(Cell.read((intensity) => Effect.andThen(intensity.record, intensity.isExceeded)), Cell.decode((intensityExceeded) => Result.succeed({
|
|
617
|
+
strategy: spec.strategy,
|
|
618
|
+
totalChildren: spec.totalChildren,
|
|
619
|
+
failedIndex: spec.failedIndex,
|
|
620
|
+
exitSuccess: false,
|
|
621
|
+
intensityExceeded
|
|
622
|
+
})), Cell.decide(decideRestart), Cell.encode((outcome) => outcome), Cell.write((outcome) => Result.match(outcome, {
|
|
623
|
+
onFailure: () => handleExhausted(spec.ctx, spec.cause),
|
|
624
|
+
onSuccess: (right) => Match.value(right).pipe(Match.tag("Continue", () => Effect.succeed(StopEpoch.make())), Match.tag("Restart", (decision) => handleRestart(spec.ctx, spec.cause, spec.onRestart(decision))), Match.exhaustive)
|
|
625
|
+
})));
|
|
626
|
+
const reopenHealthyAfterCooldown = (ctx) => Effect.andThen(ctx.health.healthy.open, Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
627
|
+
daemon: ctx.name,
|
|
628
|
+
latch: "healthy"
|
|
629
|
+
}), 1));
|
|
422
630
|
const runSupervisionEpochWithBackoff = (attempt, ctx) => Effect.gen(function* () {
|
|
423
|
-
const
|
|
631
|
+
const advance = yield* Schedule.toStep(ctx.policy.backoff);
|
|
424
632
|
const loop = () => Effect.gen(function* () {
|
|
425
|
-
const
|
|
426
|
-
return yield* Match.value(
|
|
633
|
+
const epochStep = yield* attempt.pipe(Effect.scoped);
|
|
634
|
+
return yield* Match.value(epochStep).pipe(Match.tag("StopEpoch", () => Effect.succeed(StopSupervision.make())), Match.tag("CooldownEpoch", () => Effect.gen(function* () {
|
|
427
635
|
yield* Effect.sleep(ctx.policy.cooldown);
|
|
428
636
|
yield* reopenHealthyAfterCooldown(ctx);
|
|
429
|
-
return
|
|
637
|
+
return ContinueSupervision.make();
|
|
430
638
|
})), Match.tag("RestartEpoch", () => Effect.gen(function* () {
|
|
431
|
-
const
|
|
432
|
-
|
|
639
|
+
const now = yield* Clock.currentTimeMillis;
|
|
640
|
+
const pulled = yield* Effect.result(advance(now, void 0));
|
|
641
|
+
if (Result.isFailure(pulled)) return StopSupervision.make();
|
|
642
|
+
const [, delay] = pulled.success;
|
|
643
|
+
yield* Effect.sleep(delay);
|
|
433
644
|
return yield* loop();
|
|
434
645
|
})), Match.exhaustive);
|
|
435
646
|
});
|
|
436
647
|
return yield* loop();
|
|
437
648
|
});
|
|
438
649
|
const openAllReady = (ctx) => Effect.gen(function* () {
|
|
439
|
-
yield* Effect.yieldNow
|
|
650
|
+
yield* Effect.yieldNow;
|
|
440
651
|
yield* Effect.forEach(ctx.booted, (b) => b.health.ready.await, { concurrency: "unbounded" });
|
|
441
|
-
yield* Effect.
|
|
652
|
+
yield* Effect.andThen(ctx.health.ready.open, Metric.update(Metric.withAttributes(healthStateGauge, {
|
|
653
|
+
daemon: ctx.name,
|
|
654
|
+
latch: "ready"
|
|
655
|
+
}), 1));
|
|
656
|
+
});
|
|
657
|
+
/**
|
|
658
|
+
* Records one restart against the child's intensity tracker and reports whether the
|
|
659
|
+
* child's restart budget is now exceeded. A child without a bounded intensity policy
|
|
660
|
+
* never hits the budget.
|
|
661
|
+
*/
|
|
662
|
+
const isChildIntensityBudgetDone = (tracker) => Option.match(tracker, {
|
|
663
|
+
onNone: () => Effect.succeed(false),
|
|
664
|
+
onSome: (ci) => Effect.gen(function* () {
|
|
665
|
+
yield* ci.record;
|
|
666
|
+
return yield* ci.isExceeded;
|
|
667
|
+
})
|
|
442
668
|
});
|
|
443
669
|
const superviseChild = (ctx, child, idx) => Effect.gen(function* () {
|
|
444
|
-
const childIntensityOpt = yield* Option.match(Option.
|
|
670
|
+
const childIntensityOpt = yield* Option.match(Option.fromNullishOr(child.childPolicy.intensity), {
|
|
445
671
|
onNone: () => Effect.succeed(Option.none()),
|
|
446
|
-
onSome: (cfg) => Effect.map(make(
|
|
672
|
+
onSome: (cfg) => Effect.map(make(cfg.restarts, cfg.window), Option.some)
|
|
447
673
|
});
|
|
448
674
|
const loop = () => Effect.gen(function* () {
|
|
449
675
|
const supIntensity = yield* ctx.intensityEff;
|
|
450
676
|
const attempt = Effect.gen(function* () {
|
|
451
677
|
yield* ctx.health.paused.await;
|
|
452
|
-
const fiber = yield* Effect.forkScoped(child.run);
|
|
678
|
+
const fiber = yield* Effect.forkScoped(child.run, { startImmediately: true });
|
|
453
679
|
const exit = yield* Fiber.await(fiber);
|
|
454
680
|
if (!Exit.isSuccess(exit)) {
|
|
455
|
-
if (child.childPolicy.restart === "temporary") return
|
|
456
|
-
if (yield*
|
|
457
|
-
|
|
458
|
-
onSome: (ci) => Effect.gen(function* () {
|
|
459
|
-
yield* ci.record;
|
|
460
|
-
return yield* ci.isExceeded;
|
|
461
|
-
})
|
|
462
|
-
})) return new StopEpoch();
|
|
463
|
-
yield* supIntensity.record;
|
|
464
|
-
const decision = decideRestart({
|
|
681
|
+
if (child.childPolicy.restart === "temporary") return StopEpoch.make();
|
|
682
|
+
if (yield* isChildIntensityBudgetDone(childIntensityOpt)) return StopEpoch.make();
|
|
683
|
+
return yield* Cell.apply(restartDescription({
|
|
465
684
|
strategy: "one_for_one",
|
|
466
|
-
exitSuccess: false,
|
|
467
|
-
intensityExceeded: yield* supIntensity.isExceeded,
|
|
468
685
|
failedIndex: idx,
|
|
469
|
-
totalChildren: ctx.booted.length
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
});
|
|
686
|
+
totalChildren: ctx.booted.length,
|
|
687
|
+
ctx,
|
|
688
|
+
cause: exit.cause,
|
|
689
|
+
onRestart: () => Effect.void
|
|
690
|
+
}), supIntensity);
|
|
475
691
|
}
|
|
476
|
-
return
|
|
692
|
+
return StopEpoch.make();
|
|
477
693
|
});
|
|
478
694
|
const epochResult = yield* runSupervisionEpochWithBackoff(attempt, ctx);
|
|
479
695
|
return yield* Match.value(epochResult).pipe(Match.tag("ContinueSupervision", () => loop()), Match.tag("StopSupervision", () => Effect.void), Match.exhaustive);
|
|
@@ -481,54 +697,44 @@ const superviseChild = (ctx, child, idx) => Effect.gen(function* () {
|
|
|
481
697
|
return yield* loop();
|
|
482
698
|
});
|
|
483
699
|
const runIndependent = (ctx) => Effect.gen(function* () {
|
|
484
|
-
const fibers = yield* Effect.forEach(ctx.booted, (child, childIdx) => Effect.forkScoped(superviseChild(ctx, child, childIdx)));
|
|
485
|
-
yield* Effect.yieldNow
|
|
700
|
+
const fibers = yield* Effect.forEach(ctx.booted, (child, childIdx) => Effect.forkScoped(superviseChild(ctx, child, childIdx), { startImmediately: true }));
|
|
701
|
+
yield* Effect.yieldNow;
|
|
486
702
|
yield* openAllReady(ctx);
|
|
487
703
|
yield* Effect.forEach(fibers, (f) => Fiber.await(f), { concurrency: "unbounded" });
|
|
488
704
|
});
|
|
489
705
|
const runGroup = (strategy, ctx) => Effect.gen(function* () {
|
|
490
706
|
const loop = () => Effect.gen(function* () {
|
|
491
707
|
const intensity = yield* ctx.intensityEff;
|
|
492
|
-
const childIntensityTrackers = yield* Effect.forEach(ctx.booted, (b) => Option.match(Option.
|
|
708
|
+
const childIntensityTrackers = yield* Effect.forEach(ctx.booted, (b) => Option.match(Option.fromNullishOr(b.childPolicy.intensity), {
|
|
493
709
|
onNone: () => Effect.succeed(Option.none()),
|
|
494
|
-
onSome: (cfg) => Effect.map(make(
|
|
710
|
+
onSome: (cfg) => Effect.map(make(cfg.restarts, cfg.window), Option.some)
|
|
495
711
|
}));
|
|
496
712
|
const cursor = yield* Ref.make(0);
|
|
497
713
|
const attempt = Effect.gen(function* () {
|
|
498
714
|
yield* ctx.health.paused.await;
|
|
499
715
|
const startIdx = yield* Ref.get(cursor);
|
|
500
716
|
const slice = ctx.booted.slice(startIdx);
|
|
501
|
-
const fibers = yield* Effect.forEach(slice, (c) => Effect.forkScoped(c.run));
|
|
502
|
-
yield* Effect.yieldNow
|
|
503
|
-
yield* Effect.forkScoped(openAllReady(ctx));
|
|
717
|
+
const fibers = yield* Effect.forEach(slice, (c) => Effect.forkScoped(c.run, { startImmediately: true }));
|
|
718
|
+
yield* Effect.yieldNow;
|
|
719
|
+
yield* Effect.forkScoped(openAllReady(ctx), { startImmediately: true });
|
|
504
720
|
const [failedOffset, firstExit] = yield* raceForExit(fibers);
|
|
505
721
|
if (!Exit.isSuccess(firstExit)) {
|
|
506
722
|
const failedIdx = startIdx + failedOffset;
|
|
507
|
-
const failedBootedOpt = Option.
|
|
508
|
-
if (Option.isNone(failedBootedOpt)) return
|
|
509
|
-
if (failedBootedOpt.value.childPolicy.restart === "temporary") return
|
|
510
|
-
const cIntForFailed = Option.flatten(Array
|
|
511
|
-
if (yield*
|
|
512
|
-
|
|
513
|
-
onSome: (cInt) => Effect.gen(function* () {
|
|
514
|
-
yield* cInt.record;
|
|
515
|
-
return yield* cInt.isExceeded;
|
|
516
|
-
})
|
|
517
|
-
})) return new StopEpoch();
|
|
518
|
-
yield* intensity.record;
|
|
519
|
-
const decision = decideRestart({
|
|
723
|
+
const failedBootedOpt = Option.fromNullishOr(ctx.booted[failedIdx]);
|
|
724
|
+
if (Option.isNone(failedBootedOpt)) return StopEpoch.make();
|
|
725
|
+
if (failedBootedOpt.value.childPolicy.restart === "temporary") return StopEpoch.make();
|
|
726
|
+
const cIntForFailed = Option.flatten(Array.get(childIntensityTrackers, failedIdx));
|
|
727
|
+
if (yield* isChildIntensityBudgetDone(cIntForFailed)) return StopEpoch.make();
|
|
728
|
+
return yield* Cell.apply(restartDescription({
|
|
520
729
|
strategy,
|
|
521
|
-
|
|
522
|
-
intensityExceeded: yield* intensity.isExceeded,
|
|
730
|
+
failedIndex: failedIdx,
|
|
523
731
|
totalChildren: ctx.booted.length,
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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)
|
|
529
|
-
});
|
|
732
|
+
ctx,
|
|
733
|
+
cause: firstExit.cause,
|
|
734
|
+
onRestart: (decision) => Ref.set(cursor, decision.indices[0])
|
|
735
|
+
}), intensity);
|
|
530
736
|
}
|
|
531
|
-
return
|
|
737
|
+
return StopEpoch.make();
|
|
532
738
|
});
|
|
533
739
|
const epochResult = yield* runSupervisionEpochWithBackoff(attempt, ctx);
|
|
534
740
|
return yield* Match.value(epochResult).pipe(Match.tag("ContinueSupervision", () => loop()), Match.tag("StopSupervision", () => Effect.void), Match.exhaustive);
|
|
@@ -536,24 +742,23 @@ const runGroup = (strategy, ctx) => Effect.gen(function* () {
|
|
|
536
742
|
return yield* loop();
|
|
537
743
|
});
|
|
538
744
|
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);
|
|
539
|
-
const
|
|
745
|
+
const intensityTracker = (intensity) => Match.value(intensity).pipe(Match.tag("Unbounded", () => Effect.succeed(neverExceeds)), Match.tag("Bounded", ({ restarts, window }) => make(restarts, window)), Match.exhaustive);
|
|
746
|
+
const buildSupervisorBody = (sup, health, booted, reporter) => Effect.gen(function* () {
|
|
540
747
|
const policy = yield* sup.supervision;
|
|
541
|
-
const tracker =
|
|
748
|
+
const tracker = yield* intensityTracker(policy.intensity);
|
|
542
749
|
const intensityEff = Effect.succeed(tracker);
|
|
543
750
|
const reportRestart = (cause) => Effect.gen(function* () {
|
|
544
|
-
|
|
545
|
-
yield* Metric.increment(Metric.tagged(supervisorRestartsCounter, "supervisor", sup.name));
|
|
751
|
+
yield* Metric.update(Metric.withAttributes(supervisorRestartsCounter, { supervisor: sup.name }), 1);
|
|
546
752
|
yield* reporter.onRestart(sup.name, cause);
|
|
547
|
-
yield* Option.match(Option.
|
|
753
|
+
yield* Option.match(Option.fromNullishOr(sup.reporter.onRestart), {
|
|
548
754
|
onNone: () => Effect.void,
|
|
549
755
|
onSome: (fn) => fn(cause)
|
|
550
756
|
});
|
|
551
757
|
});
|
|
552
758
|
const reportExhausted = (cause) => Effect.gen(function* () {
|
|
553
|
-
|
|
554
|
-
yield* Metric.increment(Metric.tagged(supervisorExhaustionsCounter, "supervisor", sup.name));
|
|
759
|
+
yield* Metric.update(Metric.withAttributes(supervisorExhaustionsCounter, { supervisor: sup.name }), 1);
|
|
555
760
|
yield* reporter.onExhausted(sup.name, cause);
|
|
556
|
-
yield* Option.match(Option.
|
|
761
|
+
yield* Option.match(Option.fromNullishOr(sup.reporter.onExhausted), {
|
|
557
762
|
onNone: () => Effect.void,
|
|
558
763
|
onSome: (fn) => fn(cause)
|
|
559
764
|
});
|
|
@@ -570,7 +775,7 @@ const buildSupervisorBody = (sup, health, booted) => Effect.gen(function* () {
|
|
|
570
775
|
yield* Effect.andThen(health.paused.await, runStrategy);
|
|
571
776
|
});
|
|
572
777
|
const isWorker = (x) => WorkerTypeId in x;
|
|
573
|
-
const bootChild = (child) => Effect.gen(function* () {
|
|
778
|
+
const bootChild = (child, reporter) => Effect.gen(function* () {
|
|
574
779
|
if (isWorker(child)) {
|
|
575
780
|
const health = yield* allocateWorkerHealth(child.name);
|
|
576
781
|
const loop = buildWorkerLoop(child, health, healthStateGauge).pipe(Effect.orDie);
|
|
@@ -581,9 +786,9 @@ const bootChild = (child) => Effect.gen(function* () {
|
|
|
581
786
|
childPolicy: child.child
|
|
582
787
|
};
|
|
583
788
|
}
|
|
584
|
-
const bootedChildren = yield* Effect.forEach(child.children, bootChild);
|
|
789
|
+
const bootedChildren = yield* Effect.forEach(child.children, (c) => bootChild(c, reporter));
|
|
585
790
|
const health = yield* allocateSupervisorHealth(child.name, bootedChildren.map((b) => b.health));
|
|
586
|
-
const body = buildSupervisorBody(child, health, bootedChildren).pipe(Effect.orDie);
|
|
791
|
+
const body = buildSupervisorBody(child, health, bootedChildren, reporter).pipe(Effect.orDie);
|
|
587
792
|
return {
|
|
588
793
|
name: child.name,
|
|
589
794
|
health,
|
|
@@ -591,171 +796,64 @@ const bootChild = (child) => Effect.gen(function* () {
|
|
|
591
796
|
childPolicy: {}
|
|
592
797
|
};
|
|
593
798
|
});
|
|
594
|
-
const supervisor = (s) => Effect.gen(function* () {
|
|
595
|
-
const booted = yield* Effect.forEach(s.children, bootChild);
|
|
799
|
+
const supervisor$1 = (s, reporter, binding) => Effect.gen(function* () {
|
|
800
|
+
const booted = yield* Effect.forEach(s.children, (child) => bootChild(child, reporter));
|
|
596
801
|
const health = yield* allocateSupervisorHealth(s.name, booted.map((b) => b.health));
|
|
597
|
-
const body = buildSupervisorBody(s, health, booted).pipe(Effect.orDie);
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
else if (s.lock.mode === "required") locked = withLeaderLock(body, {
|
|
601
|
-
key: s.lock.key,
|
|
602
|
-
mode: "required",
|
|
603
|
-
acquireRetryBackoff: s.lock.acquireRetryBackoff
|
|
604
|
-
});
|
|
605
|
-
else locked = withLeaderLock(body, {
|
|
606
|
-
key: s.lock.key,
|
|
607
|
-
mode: "optional"
|
|
608
|
-
});
|
|
609
|
-
yield* Effect.forkScoped(locked.pipe(Effect.orDie));
|
|
802
|
+
const body = buildSupervisorBody(s, health, booted, reporter).pipe(Effect.orDie);
|
|
803
|
+
const locked = withLockByMode(body, binding);
|
|
804
|
+
yield* Effect.forkScoped(locked.pipe(Effect.orDie), { startImmediately: true });
|
|
610
805
|
return health;
|
|
611
806
|
});
|
|
612
|
-
Context.Tag("@systemfsoftware/effect-daemon-spec/internal/build-dynamic.executor/BuildDynamicExecutorDeps")();
|
|
613
|
-
const buildDynamic = (spec, health) => Effect.gen(function* () {
|
|
614
|
-
const state = yield* Ref.make({
|
|
615
|
-
nextId: 0,
|
|
616
|
-
children: HashMap.empty()
|
|
617
|
-
});
|
|
618
|
-
const startChildImpl = (args) => Effect.gen(function* () {
|
|
619
|
-
const childWorker = spec.child(args);
|
|
620
|
-
const loop = buildWorkerLoop(childWorker, yield* allocateWorkerHealth(childWorker.name), healthStateGauge).pipe(Effect.orDie);
|
|
621
|
-
const removed = yield* Effect.makeLatch(false);
|
|
622
|
-
const reservedId = yield* Ref.modify(state, (current) => {
|
|
623
|
-
if (HashMap.size(current.children) >= spec.maxChildren) return [Option.none(), current];
|
|
624
|
-
const children = HashMap.set(current.children, current.nextId, {
|
|
625
|
-
fiber: Option.none(),
|
|
626
|
-
removed
|
|
627
|
-
});
|
|
628
|
-
return [Option.some(current.nextId), {
|
|
629
|
-
nextId: current.nextId + 1,
|
|
630
|
-
children
|
|
631
|
-
}];
|
|
632
|
-
});
|
|
633
|
-
if (Option.isNone(reservedId)) return yield* new DynamicLimitExceeded({ limit: spec.maxChildren });
|
|
634
|
-
const id = reservedId.value;
|
|
635
|
-
yield* Metric.set(supervisorChildrenGauge, HashMap.size(yield* Ref.get(state).pipe(Effect.map((s) => s.children))));
|
|
636
|
-
const cleanup = Effect.gen(function* () {
|
|
637
|
-
const count = yield* Ref.modify(state, (current) => {
|
|
638
|
-
const children = HashMap.remove(current.children, id);
|
|
639
|
-
return [HashMap.size(children), {
|
|
640
|
-
...current,
|
|
641
|
-
children
|
|
642
|
-
}];
|
|
643
|
-
});
|
|
644
|
-
yield* Metric.set(supervisorChildrenGauge, count);
|
|
645
|
-
yield* removed.open;
|
|
646
|
-
}).pipe(Effect.asVoid);
|
|
647
|
-
const fiber = yield* Effect.forkScoped(loop.pipe(Effect.ensuring(cleanup)));
|
|
648
|
-
const count = yield* Ref.modify(state, (current) => {
|
|
649
|
-
const childOpt = HashMap.get(current.children, id);
|
|
650
|
-
if (Option.isNone(childOpt)) return [HashMap.size(current.children), current];
|
|
651
|
-
const children = HashMap.set(current.children, id, {
|
|
652
|
-
...childOpt.value,
|
|
653
|
-
fiber: Option.some(fiber)
|
|
654
|
-
});
|
|
655
|
-
return [HashMap.size(children), {
|
|
656
|
-
nextId: current.nextId,
|
|
657
|
-
children
|
|
658
|
-
}];
|
|
659
|
-
});
|
|
660
|
-
yield* Metric.set(supervisorChildrenGauge, count);
|
|
661
|
-
return {
|
|
662
|
-
id,
|
|
663
|
-
removed: removed.await
|
|
664
|
-
};
|
|
665
|
-
});
|
|
666
|
-
const stopChildImpl = (ref) => Effect.gen(function* () {
|
|
667
|
-
const [stateOpt, count] = yield* Ref.modify(state, (current) => {
|
|
668
|
-
const found = HashMap.get(current.children, ref.id);
|
|
669
|
-
const children = HashMap.remove(current.children, ref.id);
|
|
670
|
-
return [[found, HashMap.size(children)], {
|
|
671
|
-
...current,
|
|
672
|
-
children
|
|
673
|
-
}];
|
|
674
|
-
});
|
|
675
|
-
if (Option.isSome(stateOpt)) {
|
|
676
|
-
const { fiber, removed } = stateOpt.value;
|
|
677
|
-
yield* Option.match(fiber, {
|
|
678
|
-
onNone: () => Effect.void,
|
|
679
|
-
onSome: (running) => Effect.gen(function* () {
|
|
680
|
-
yield* Fiber.interrupt(running);
|
|
681
|
-
yield* Fiber.await(running);
|
|
682
|
-
})
|
|
683
|
-
});
|
|
684
|
-
yield* Metric.set(supervisorChildrenGauge, count);
|
|
685
|
-
yield* removed.open;
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
const countImpl = Ref.get(state).pipe(Effect.map((current) => HashMap.size(current.children)));
|
|
689
|
-
yield* Effect.zipRight(health.ready.open, Metric.set(Metric.tagged(Metric.tagged(healthStateGauge, "daemon", spec.name), "latch", "ready"), 1));
|
|
690
|
-
return {
|
|
691
|
-
health,
|
|
692
|
-
startChild: startChildImpl,
|
|
693
|
-
stopChild: stopChildImpl,
|
|
694
|
-
count: countImpl
|
|
695
|
-
};
|
|
696
|
-
});
|
|
697
|
-
const dynamic$2 = (spec) => Effect.gen(function* () {
|
|
698
|
-
const health = yield* allocateSupervisorHealth(spec.name, []);
|
|
699
|
-
return yield* buildDynamic(spec, health);
|
|
700
|
-
});
|
|
701
807
|
//#endregion
|
|
702
|
-
//#region src/internal/
|
|
703
|
-
|
|
808
|
+
//#region src/internal/SupervisionLeader.ts
|
|
809
|
+
const LeaderConfig = Context.Reference("@systemfsoftware/effect-daemon-spec/LeaderConfig", { defaultValue: () => ({
|
|
704
810
|
backoffBase: Duration.seconds(1),
|
|
705
|
-
intensity:
|
|
811
|
+
intensity: UnboundedIntensity.make(),
|
|
706
812
|
cooldown: Duration.zero
|
|
707
|
-
}) })
|
|
813
|
+
}) });
|
|
708
814
|
//#endregion
|
|
709
|
-
//#region src/internal/
|
|
710
|
-
|
|
815
|
+
//#region src/internal/SupervisionTask.ts
|
|
816
|
+
const TaskConfig = Context.Reference("@systemfsoftware/effect-daemon-spec/TaskConfig", { defaultValue: () => ({
|
|
711
817
|
backoffBase: Duration.seconds(1),
|
|
712
|
-
intensity:
|
|
818
|
+
intensity: UnboundedIntensity.make(),
|
|
713
819
|
cooldown: Duration.zero
|
|
714
|
-
}) })
|
|
820
|
+
}) });
|
|
715
821
|
//#endregion
|
|
716
|
-
//#region src/internal/
|
|
717
|
-
|
|
822
|
+
//#region src/internal/SupervisionWorker.ts
|
|
823
|
+
const WorkerConfig = Context.Reference("@systemfsoftware/effect-daemon-spec/WorkerConfig", { defaultValue: () => ({
|
|
718
824
|
backoffBase: Duration.seconds(10),
|
|
719
|
-
intensity:
|
|
825
|
+
intensity: BoundedIntensity.make({
|
|
720
826
|
restarts: 10,
|
|
721
827
|
window: Duration.seconds(60)
|
|
722
828
|
}),
|
|
723
829
|
cooldown: Duration.seconds(30)
|
|
724
|
-
}) })
|
|
830
|
+
}) });
|
|
725
831
|
//#endregion
|
|
726
|
-
//#region src/
|
|
832
|
+
//#region src/SupervisionCustom.ts
|
|
727
833
|
const custom = (policy) => Effect.succeed(policy);
|
|
728
834
|
//#endregion
|
|
729
|
-
//#region src/
|
|
835
|
+
//#region src/SupervisionLeader.ts
|
|
730
836
|
const leader$1 = (config, cap) => Effect.succeed({
|
|
731
837
|
intensity: config.intensity,
|
|
732
838
|
backoff: cappedBackoff(config.backoffBase, cap),
|
|
733
839
|
cooldown: config.cooldown
|
|
734
840
|
});
|
|
735
841
|
//#endregion
|
|
736
|
-
//#region src/
|
|
842
|
+
//#region src/SupervisionTask.ts
|
|
737
843
|
const task$1 = (config, budget) => Effect.succeed({
|
|
738
844
|
intensity: config.intensity,
|
|
739
|
-
backoff: Schedule.exponential(config.backoffBase).pipe(Schedule.jittered, Schedule.upTo(budget)),
|
|
845
|
+
backoff: Schedule.exponential(config.backoffBase).pipe(Schedule.jittered, Schedule.upTo({ duration: budget })),
|
|
740
846
|
cooldown: config.cooldown
|
|
741
847
|
});
|
|
742
848
|
//#endregion
|
|
743
|
-
//#region src/
|
|
849
|
+
//#region src/SupervisionWorker.ts
|
|
744
850
|
const worker$1 = (config, cap) => Effect.succeed({
|
|
745
851
|
intensity: config.intensity,
|
|
746
852
|
backoff: cappedBackoff(config.backoffBase, cap),
|
|
747
853
|
cooldown: config.cooldown
|
|
748
854
|
});
|
|
749
855
|
//#endregion
|
|
750
|
-
//#region src/
|
|
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
|
|
856
|
+
//#region src/SupervisorOneForAll.ts
|
|
759
857
|
const oneForAll$1 = (opts) => ({
|
|
760
858
|
[SupervisorTypeId]: SupervisorTypeId,
|
|
761
859
|
name: opts.name,
|
|
@@ -766,7 +864,7 @@ const oneForAll$1 = (opts) => ({
|
|
|
766
864
|
reporter: opts.reporter ?? {}
|
|
767
865
|
});
|
|
768
866
|
//#endregion
|
|
769
|
-
//#region src/
|
|
867
|
+
//#region src/SupervisorOneForOne.ts
|
|
770
868
|
const oneForOne$1 = (opts) => ({
|
|
771
869
|
[SupervisorTypeId]: SupervisorTypeId,
|
|
772
870
|
name: opts.name,
|
|
@@ -777,7 +875,7 @@ const oneForOne$1 = (opts) => ({
|
|
|
777
875
|
reporter: opts.reporter ?? {}
|
|
778
876
|
});
|
|
779
877
|
//#endregion
|
|
780
|
-
//#region src/
|
|
878
|
+
//#region src/SupervisorRestForOne.ts
|
|
781
879
|
const restForOne$1 = (opts) => ({
|
|
782
880
|
[SupervisorTypeId]: SupervisorTypeId,
|
|
783
881
|
name: opts.name,
|
|
@@ -797,10 +895,49 @@ const Daemon = {
|
|
|
797
895
|
stream,
|
|
798
896
|
subscription
|
|
799
897
|
};
|
|
898
|
+
/**
|
|
899
|
+
* Boots a worker. The leader-lock capability is acquired here, at the composition
|
|
900
|
+
* root, and handed down as part of the lock binding: the executor behind this
|
|
901
|
+
* entry point never sees the tag. A worker whose lock is `{ mode: 'none' }`
|
|
902
|
+
* takes no lock at all.
|
|
903
|
+
*/
|
|
904
|
+
const worker = (w) => Effect.gen(function* () {
|
|
905
|
+
let binding;
|
|
906
|
+
if (isModeNone(w.lock)) binding = { kind: "unlocked" };
|
|
907
|
+
else {
|
|
908
|
+
const lock = yield* LeaderLock;
|
|
909
|
+
binding = {
|
|
910
|
+
kind: "locked",
|
|
911
|
+
spec: w.lock,
|
|
912
|
+
lock
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
return yield* worker$2(w, binding);
|
|
916
|
+
});
|
|
917
|
+
/**
|
|
918
|
+
* The supervisor: acquires the `DaemonReporter` and — unless the lock mode is none —
|
|
919
|
+
* the `LeaderLock` capabilities at the composition root, then hands them down to the
|
|
920
|
+
* supervisor body via the lock binding. The body itself only ever sees the service
|
|
921
|
+
* values.
|
|
922
|
+
*/
|
|
923
|
+
const supervisor = (s) => Effect.gen(function* () {
|
|
924
|
+
const reporter = yield* DaemonReporter;
|
|
925
|
+
let binding;
|
|
926
|
+
if (isModeNone(s.lock)) binding = { kind: "unlocked" };
|
|
927
|
+
else {
|
|
928
|
+
const lock = yield* LeaderLock;
|
|
929
|
+
binding = {
|
|
930
|
+
kind: "locked",
|
|
931
|
+
spec: s.lock,
|
|
932
|
+
lock
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
return yield* supervisor$1(s, reporter, binding);
|
|
936
|
+
});
|
|
800
937
|
const run = {
|
|
801
938
|
worker,
|
|
802
939
|
supervisor,
|
|
803
|
-
dynamic: dynamic$
|
|
940
|
+
dynamic: dynamic$1
|
|
804
941
|
};
|
|
805
942
|
const leader = (cap) => Effect.flatMap(LeaderConfig, (config) => leader$1(config, cap));
|
|
806
943
|
const task = (budget) => Effect.flatMap(TaskConfig, (config) => task$1(config, budget));
|
|
@@ -811,19 +948,12 @@ const Supervision = {
|
|
|
811
948
|
task,
|
|
812
949
|
custom
|
|
813
950
|
};
|
|
814
|
-
const dynamic = (opts) => dynamic$
|
|
951
|
+
const dynamic = (opts) => dynamic$2({
|
|
952
|
+
...opts,
|
|
953
|
+
maxChildren: opts.maxChildren ?? MaxChildren.make(1e3)
|
|
954
|
+
});
|
|
815
955
|
const oneForAll = (opts) => oneForAll$1(opts);
|
|
816
956
|
const oneForOne = (opts) => oneForOne$1(opts);
|
|
817
957
|
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
958
|
//#endregion
|
|
829
|
-
export { BoundedIntensity, ChildPolicyConfig, Daemon, DaemonReporter, DynamicLimitExceeded, DynamicSpecTypeId, Intensity, IntensityConfig, LeaderConfig, LeaderLock, LeaderLockFromPrimitive, LeaderLockInfraError, LeaderLockNotAcquired, LockPolicyConfig, LockPrimitive, LockPrimitiveError, Noop, Supervision,
|
|
959
|
+
export { BoundedIntensity, ChildPolicyConfig, Daemon, DaemonReporter, DynamicLimitExceeded, DynamicSpecTypeId, Intensity, IntensityConfig, LeaderConfig, LeaderLock, LeaderLockFromPrimitive, LeaderLockInfraError, LeaderLockNotAcquired, LockPolicyConfig, LockPrimitive, LockPrimitiveError, MaxChildren, Noop, Supervision, SupervisorTypeId, TaskConfig, TickPolicyConfig, UnboundedIntensity, WorkerConfig, WorkerTypeId, cappedBackoff, dynamic, healthStateGauge, leader, oneForAll, oneForOne, poll, restForOne, run, stream, subscription, supervision, supervisor, supervisorChildrenGauge, supervisorExhaustionsCounter, supervisorRestartsCounter, task, withLeaderLock, worker };
|