@effect-agent/platform-cloudflare 0.1.0-beta.113 → 0.1.0-beta.115
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/Alarm.d.mts +32 -48
- package/dist/Alarm.mjs +173 -175
- package/dist/Alarm.mjs.map +1 -1
- package/dist/CloudflareThreadClient.d.mts +45 -30
- package/dist/ProtectedBrowser.mjs +2 -1
- package/dist/ProtectedBrowser.mjs.map +1 -1
- package/dist/{ThreadObject-BPt5uByD.mjs → ThreadObject-CEWwifrL.mjs} +10 -29
- package/dist/ThreadObject-CEWwifrL.mjs.map +1 -0
- package/dist/{ThreadObject-DAuaRyGl.d.mts → ThreadObject-HwODpdru.d.mts} +22 -22
- package/dist/ThreadObject.d.mts +1 -1
- package/dist/ThreadObject.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/Alarm.ts +291 -350
- package/src/ThreadObject.ts +4 -0
- package/src/internal/layers.ts +8 -0
- package/src/internal/message-delivery.ts +4 -64
- package/src/protected-browser/inspect-frame.ts +2 -1
- package/dist/ThreadObject-BPt5uByD.mjs.map +0 -1
package/dist/Alarm.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DurableObjectContext } from "./CloudflareBindings.mjs";
|
|
2
2
|
import { s as CloudflareDurableRuntimeConfig } from "./CloudflareConfig-DKXGo8L3.mjs";
|
|
3
|
-
import { Cause, Context,
|
|
3
|
+
import { Cause, Context, Effect, Layer, Option, Schema, Scope } from "effect";
|
|
4
4
|
import { DurableBindingFailure } from "effect-agent/agent-registration";
|
|
5
5
|
import { DurableAgentRuntime, DurableRuntimeConfig, DurableWorkerFailure, RecoveryFailure } from "effect-agent/durable-agent-runtime";
|
|
6
6
|
import { ThreadId } from "effect-agent/identifiers";
|
|
@@ -10,7 +10,7 @@ import { ThreadProjectionError, ThreadProjectionMaintenance } from "effect-agent
|
|
|
10
10
|
import { WakeScheduler } from "effect-agent/wake-scheduler";
|
|
11
11
|
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
12
12
|
declare namespace Alarm_d_exports {
|
|
13
|
-
export { DurableAlarmError, DurableAlarmService, MaintenancePassFailure, MaintenancePassReport, ThreadHostMaintenance,
|
|
13
|
+
export { DurableAlarmError, DurableAlarmService, MaintenancePassFailure, MaintenancePassReport, ThreadHostMaintenance, ThreadHostMaintenanceLane, ThreadMaintenance, ThreadMaintenanceFailpoint, ThreadMaintenanceFailpointHandler, ThreadMaintenanceFailpointLocation, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadPublicationService, ThreadRecoveryFault, publishCommitted };
|
|
14
14
|
}
|
|
15
15
|
declare const DurableAlarmError_base: Schema.Class<DurableAlarmError, Schema.TaggedStruct<"DurableAlarmError", {
|
|
16
16
|
readonly operation: Schema.String;
|
|
@@ -114,61 +114,44 @@ declare const ThreadPublication_base: Context.ServiceClass<ThreadPublication, "@
|
|
|
114
114
|
export declare class ThreadPublication extends ThreadPublication_base {
|
|
115
115
|
static readonly layer: Layer.Layer<ThreadPublication, never, never>;
|
|
116
116
|
}
|
|
117
|
-
declare const ThreadMaintenanceActivity_base: Context.ServiceClass<ThreadMaintenanceActivity, "@effect-agent/platform-cloudflare/ThreadMaintenanceActivity", {
|
|
118
|
-
/**
|
|
119
|
-
* Account for one finite selection/dispatch, through joined completion or interruption.
|
|
120
|
-
* Register before reading/claiming work. After dispatch closes the body is not started.
|
|
121
|
-
* Host waves retain their declared allowance from this call; message Claims keep the driver's
|
|
122
|
-
* own deadline. Waiting for notifications belongs outside this bracket.
|
|
123
|
-
*/
|
|
124
|
-
readonly run: <R>(wave: Effect.Effect<void, DurableAlarmError, R>) => Effect.Effect<void, DurableAlarmError, R>;
|
|
125
|
-
/** Acknowledge all initial subscriptions/scans. Composite hosts use `all` below. */
|
|
126
|
-
readonly ready: Effect.Effect<void>;
|
|
127
|
-
/**
|
|
128
|
-
* Acquire an independent scoped subscription before the initial scan and `ready`.
|
|
129
|
-
* The returned wait observes coalesced native scan hints, including those published before
|
|
130
|
-
* its first execution. Each hint requests a current local due-work check.
|
|
131
|
-
*/
|
|
132
|
-
readonly subscribeChanges: Effect.Effect<Effect.Effect<void>, never, Scope.Scope>;
|
|
133
|
-
}>;
|
|
134
|
-
/** Event-local accounting shared by the existing maintenance pumps and native scheduler. */
|
|
135
|
-
export declare class ThreadMaintenanceActivity extends ThreadMaintenanceActivity_base {
|
|
136
|
-
/** Join child pumps; acknowledge the parent only after every child is ready or has exited. */
|
|
137
|
-
static readonly all: <A, E, R>(lanes: readonly Effect.Effect<A, E, R>[]) => Effect.Effect<readonly A[], E, R | ThreadMaintenanceActivity>;
|
|
138
|
-
}
|
|
139
117
|
/**
|
|
140
|
-
* Host-assembled native message recovery.
|
|
141
|
-
*
|
|
142
|
-
*
|
|
118
|
+
* Host-assembled native message recovery. Preparation is a bounded local selection; the
|
|
119
|
+
* driver bounds each actual Claim and persists its timeout/retry before `run` returns.
|
|
120
|
+
* The alarm must not add a timer starting at selection: Claim setup and retry commits belong
|
|
121
|
+
* to the driver. The prepared allowance decides only whether a wave fits this event.
|
|
143
122
|
*/
|
|
144
123
|
export declare const ThreadMessageDelivery: Context.Reference<{
|
|
145
|
-
readonly
|
|
124
|
+
readonly prepare: Effect.Effect<{
|
|
125
|
+
readonly timeoutMillis: number;
|
|
126
|
+
readonly run: Effect.Effect<void, DurableAlarmError>;
|
|
127
|
+
}, DurableAlarmError>;
|
|
146
128
|
readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;
|
|
147
129
|
}>;
|
|
148
130
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* Passive subscriptions do not count as active work. Closure stops both new waves and native
|
|
154
|
-
* Attempts; local admission/control subscriptions belong to the event Scope until teardown.
|
|
155
|
-
* No deadline sleeps or automatic retry loops. Return after already-admitted waves finish.
|
|
131
|
+
* One independently schedulable application obligation. `run` selects and joins one finite
|
|
132
|
+
* wave, including its retry/receipt commits. The alarm gives every lane an initial opportunity,
|
|
133
|
+
* then checks its local pending deadline on completion, native progress, wakes and bounded scans.
|
|
134
|
+
* No subscriptions, readiness acknowledgements, deadline sleeps or dispatch loops are needed.
|
|
156
135
|
*
|
|
157
|
-
* Declare a
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
136
|
+
* Declare a whole-wave allowance (1..300000ms), including selection, local commits and cleanup. A wave
|
|
137
|
+
* starts only if that allowance fits the event and later arrivals cannot renew it. A failed
|
|
138
|
+
* lane is not retried within the event. The alarm joins admitted work and closes its Scope
|
|
139
|
+
* before reading durable deadlines under the shared ThreadMutationGate.
|
|
161
140
|
*
|
|
162
|
-
*
|
|
163
|
-
* dispatch;
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* alarm slot. Required native publication gates belong to ThreadPublication.
|
|
141
|
+
* `pendingDeadline` is bounded local control state, never a retained-history scan. Persist
|
|
142
|
+
* envelopes/claims before dispatch; cancellation is local, not remote rollback. Network waits
|
|
143
|
+
* and finalizers must be interruptible; short atomic commits may be uninterruptible. Mutations
|
|
144
|
+
* use the shared gate and producers notify the existing WakeScheduler after commit. Hooks never
|
|
145
|
+
* write the raw alarm slot. Required native publication gates belong to ThreadPublication.
|
|
167
146
|
*/
|
|
168
|
-
export
|
|
147
|
+
export interface ThreadHostMaintenanceLane {
|
|
169
148
|
readonly dispatchTimeoutMillis: number;
|
|
170
|
-
readonly
|
|
149
|
+
readonly run: Effect.Effect<void, DurableAlarmError, Scope.Scope>;
|
|
171
150
|
readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;
|
|
151
|
+
}
|
|
152
|
+
/** Independent lanes share the existing alarm; compose hosts by concatenating their lanes. */
|
|
153
|
+
export declare const ThreadHostMaintenance: Context.Reference<{
|
|
154
|
+
readonly lanes: ReadonlyArray<ThreadHostMaintenanceLane>;
|
|
172
155
|
}>;
|
|
173
156
|
/** @internal A committed source operation must not become a failed operation because delivery failed. */
|
|
174
157
|
export declare const publishCommitted: Effect.Effect<void, never, ThreadPublication>;
|
|
@@ -240,10 +223,11 @@ declare const ThreadMaintenance_base: Context.ServiceClass<ThreadMaintenance, "@
|
|
|
240
223
|
* 1. Prearm before any work. A caught-up native step uses the O(1) generation record without
|
|
241
224
|
* recovery, ledger scans or canonical-history reads.
|
|
242
225
|
* 2. Reconcile before each head Attempt, then checkpoint only the observed generation. A racing
|
|
243
|
-
* producer keeps its
|
|
226
|
+
* producer keeps its generation dirty and immediately eligible. Quiescent native retries
|
|
227
|
+
* retain their durable backoff.
|
|
244
228
|
* 3. Keep native and delivery admission open together while finite waves remain active, so
|
|
245
229
|
* fresh replies and abort controls can progress during unrelated cleanup. Close atomically
|
|
246
|
-
* at quiescence, or at the original ten-minute yield deadline,
|
|
230
|
+
* at quiescence, or at the original ten-minute yield deadline, then join admitted waves.
|
|
247
231
|
* 4. Native message delivery retains its driver-owned Claim deadline. Host/backfill waves are
|
|
248
232
|
* bounded independently; incoming native work never restarts or cancels their attempts.
|
|
249
233
|
* Auxiliary failures are reported after the current native opportunity.
|
package/dist/Alarm.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
|
2
2
|
import { DurableObjectContext } from "./CloudflareBindings.mjs";
|
|
3
3
|
import { AuxiliaryDispatchMillis, CloudflareDurableRuntimeConfig } from "./CloudflareConfig.mjs";
|
|
4
4
|
import { r as safeCauseMessage } from "./boundary-BguazVkh.mjs";
|
|
5
|
-
import { Cause, Clock, Context, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option,
|
|
5
|
+
import { Cause, Clock, Context, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option, Random, Ref, Result, Schema, Scope, Semaphore, Stream, Struct } from "effect";
|
|
6
6
|
import "effect-agent/agent-registration";
|
|
7
7
|
import { DurableAgentRuntime, DurableRuntimeConfig, RecoveryBlocked, RecoveryFailure, RecoverySweepResult, isolateRecovery } from "effect-agent/durable-agent-runtime";
|
|
8
8
|
import { SubmissionId, ThreadId } from "effect-agent/identifiers";
|
|
@@ -18,7 +18,6 @@ var Alarm_exports = /* @__PURE__ */ __exportAll({
|
|
|
18
18
|
MaintenancePassReport: () => MaintenancePassReport,
|
|
19
19
|
ThreadHostMaintenance: () => ThreadHostMaintenance,
|
|
20
20
|
ThreadMaintenance: () => ThreadMaintenance,
|
|
21
|
-
ThreadMaintenanceActivity: () => ThreadMaintenanceActivity,
|
|
22
21
|
ThreadMaintenanceFailpoint: () => ThreadMaintenanceFailpoint,
|
|
23
22
|
ThreadMessageDelivery: () => ThreadMessageDelivery,
|
|
24
23
|
ThreadMutationGate: () => ThreadMutationGate,
|
|
@@ -112,61 +111,21 @@ var ThreadPublication = class extends Context.Service()("@effect-agent/platform-
|
|
|
112
111
|
pendingDeadline: Effect.succeed(Option.none())
|
|
113
112
|
});
|
|
114
113
|
};
|
|
115
|
-
/** Event-local accounting shared by the existing maintenance pumps and native scheduler. */
|
|
116
|
-
var ThreadMaintenanceActivity = class ThreadMaintenanceActivity extends Context.Service()("@effect-agent/platform-cloudflare/ThreadMaintenanceActivity") {
|
|
117
|
-
/** Join child pumps; acknowledge the parent only after every child is ready or has exited. */
|
|
118
|
-
static all = Effect.fnUntraced(function* (lanes) {
|
|
119
|
-
const activity = yield* ThreadMaintenanceActivity;
|
|
120
|
-
let remaining = lanes.length;
|
|
121
|
-
if (remaining === 0) return yield* activity.ready.pipe(Effect.as([]));
|
|
122
|
-
return yield* Effect.forEach(lanes, (lane) => {
|
|
123
|
-
let ready = false;
|
|
124
|
-
const child = {
|
|
125
|
-
...activity,
|
|
126
|
-
ready: Effect.suspend(() => {
|
|
127
|
-
if (ready) return Effect.void;
|
|
128
|
-
ready = true;
|
|
129
|
-
return --remaining === 0 ? activity.ready : Effect.void;
|
|
130
|
-
}).pipe(Effect.uninterruptible)
|
|
131
|
-
};
|
|
132
|
-
return lane.pipe(Effect.provideService(ThreadMaintenanceActivity, child), Effect.onExit(() => child.ready));
|
|
133
|
-
}, { concurrency: "unbounded" });
|
|
134
|
-
});
|
|
135
|
-
};
|
|
136
114
|
/**
|
|
137
|
-
* Host-assembled native message recovery.
|
|
138
|
-
*
|
|
139
|
-
*
|
|
115
|
+
* Host-assembled native message recovery. Preparation is a bounded local selection; the
|
|
116
|
+
* driver bounds each actual Claim and persists its timeout/retry before `run` returns.
|
|
117
|
+
* The alarm must not add a timer starting at selection: Claim setup and retry commits belong
|
|
118
|
+
* to the driver. The prepared allowance decides only whether a wave fits this event.
|
|
140
119
|
*/
|
|
141
120
|
const ThreadMessageDelivery = Context.Reference("@effect-agent/platform-cloudflare/ThreadMessageDelivery", { defaultValue: () => ({
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
* Application obligations sharing this Object's alarm. Admit one initial external wave even on
|
|
147
|
-
* a caught-up pass, then respond to wakes and ThreadMaintenanceActivity.subscribeChanges until
|
|
148
|
-
* dispatchClosed. Yield the event's ThreadMaintenanceActivity to bracket each finite lane with
|
|
149
|
-
* run and acknowledge initial setup with ready.
|
|
150
|
-
* Passive subscriptions do not count as active work. Closure stops both new waves and native
|
|
151
|
-
* Attempts; local admission/control subscriptions belong to the event Scope until teardown.
|
|
152
|
-
* No deadline sleeps or automatic retry loops. Return after already-admitted waves finish.
|
|
153
|
-
*
|
|
154
|
-
* Declare a finite whole-wave allowance (1..300000ms): maximum for parallel lanes, sum for
|
|
155
|
-
* sequential operations. Admit a wave only if its allowance fits before dispatchUntil. Later
|
|
156
|
-
* arrivals cannot renew an admitted wave's allowance. Maintenance bounds each registered wave,
|
|
157
|
-
* interrupts and joins event Scope, then reads local deadlines under the mutation gate.
|
|
158
|
-
*
|
|
159
|
-
* Setup and pendingDeadline are bounded local operations. Persist claims/envelopes before
|
|
160
|
-
* dispatch; interrupted waits leave exact retries/receipts recoverable. Cancellation is local,
|
|
161
|
-
* not remote rollback. Retry accounting is unchanged. Network waits/finalizers must be
|
|
162
|
-
* interruptible; short local atomic commits may be uninterruptible. Hooks never write the raw
|
|
163
|
-
* alarm slot. Required native publication gates belong to ThreadPublication.
|
|
164
|
-
*/
|
|
165
|
-
const ThreadHostMaintenance = Context.Reference("@effect-agent/platform-cloudflare/ThreadHostMaintenance", { defaultValue: () => ({
|
|
166
|
-
dispatchTimeoutMillis: 1,
|
|
167
|
-
drainUntil: () => Effect.void,
|
|
121
|
+
prepare: Effect.succeed({
|
|
122
|
+
timeoutMillis: 1,
|
|
123
|
+
run: Effect.void
|
|
124
|
+
}),
|
|
168
125
|
pendingDeadline: Effect.succeed(Option.none())
|
|
169
126
|
}) });
|
|
127
|
+
/** Independent lanes share the existing alarm; compose hosts by concatenating their lanes. */
|
|
128
|
+
const ThreadHostMaintenance = Context.Reference("@effect-agent/platform-cloudflare/ThreadHostMaintenance", { defaultValue: () => ({ lanes: [] }) });
|
|
170
129
|
const earliestDeadline = (left, right) => Option.isSome(left) ? Option.isSome(right) ? Option.some(Math.min(left.value, right.value)) : left : right;
|
|
171
130
|
/** @internal A committed source operation must not become a failed operation because delivery failed. */
|
|
172
131
|
const publishCommitted = Effect.gen(function* () {
|
|
@@ -309,10 +268,11 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
|
|
|
309
268
|
* 1. Prearm before any work. A caught-up native step uses the O(1) generation record without
|
|
310
269
|
* recovery, ledger scans or canonical-history reads.
|
|
311
270
|
* 2. Reconcile before each head Attempt, then checkpoint only the observed generation. A racing
|
|
312
|
-
* producer keeps its
|
|
271
|
+
* producer keeps its generation dirty and immediately eligible. Quiescent native retries
|
|
272
|
+
* retain their durable backoff.
|
|
313
273
|
* 3. Keep native and delivery admission open together while finite waves remain active, so
|
|
314
274
|
* fresh replies and abort controls can progress during unrelated cleanup. Close atomically
|
|
315
|
-
* at quiescence, or at the original ten-minute yield deadline,
|
|
275
|
+
* at quiescence, or at the original ten-minute yield deadline, then join admitted waves.
|
|
316
276
|
* 4. Native message delivery retains its driver-owned Claim deadline. Host/backfill waves are
|
|
317
277
|
* bounded independently; incoming native work never restarts or cancels their attempts.
|
|
318
278
|
* Auxiliary failures are reported after the current native opportunity.
|
|
@@ -337,7 +297,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
|
|
|
337
297
|
const authorizer = yield* OperationAuthorizer;
|
|
338
298
|
const projectionDeadline = projection.pendingDeadline.pipe(Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.logError("Thread projection deadline unavailable", cause).pipe(Effect.as(Option.some(0)))));
|
|
339
299
|
const pendingDeadline = Effect.gen(function* () {
|
|
340
|
-
return earliestDeadline(earliestDeadline(yield* publication.pendingDeadline, yield* messages.pendingDeadline), earliestDeadline(yield* projectionDeadline, yield* host.pendingDeadline));
|
|
300
|
+
return earliestDeadline(earliestDeadline(yield* publication.pendingDeadline, yield* messages.pendingDeadline), earliestDeadline(yield* projectionDeadline, (yield* Effect.forEach(host.lanes, (lane) => lane.pendingDeadline)).reduce(earliestDeadline, Option.none())));
|
|
341
301
|
});
|
|
342
302
|
const maintenancePassGate = yield* Semaphore.make(1);
|
|
343
303
|
const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));
|
|
@@ -632,13 +592,15 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
|
|
|
632
592
|
yield* failpoint.hit("maintenance:checkpoint:before");
|
|
633
593
|
const nextAttemptAt = yield* mutations.withSnapshot((active) => runTransaction("checkpoint native maintenance", () => ctx.storage.transaction(async (transaction) => {
|
|
634
594
|
const { state } = await readMaintenanceState(transaction);
|
|
635
|
-
const
|
|
595
|
+
const mutationOverlap = observation.activeAtStart > 0 || started.activeAtStart > 0 || active > 0;
|
|
596
|
+
const processed = autonomous || mutationOverlap ? state.processed : state.processed > observation.generation ? state.processed : observation.generation;
|
|
597
|
+
const canBackoff = !mutationOverlap && state.dirty === started.generation;
|
|
636
598
|
const next = ThreadMaintenanceState.make({
|
|
637
599
|
...Struct.omit(state, ["retry"]),
|
|
638
600
|
processed,
|
|
639
601
|
nonterminal: remaining.length,
|
|
640
602
|
bindingRetries: (state.bindingRetries ?? []).filter((retry) => remaining.some((row) => row.submissionId === retry.submissionId)),
|
|
641
|
-
...autonomous && !progressed ? { retry: MaintenanceRetry.make({
|
|
603
|
+
...autonomous && !progressed && canBackoff ? { retry: MaintenanceRetry.make({
|
|
642
604
|
generation: started.generation,
|
|
643
605
|
notBefore: now + delay,
|
|
644
606
|
nativeOnly: true,
|
|
@@ -646,8 +608,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
|
|
|
646
608
|
}) } : {}
|
|
647
609
|
});
|
|
648
610
|
await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));
|
|
649
|
-
if (autonomous) return
|
|
650
|
-
return
|
|
611
|
+
if (autonomous) return now + (canBackoff ? delay : minimumAlarmDelay);
|
|
612
|
+
return mutationOverlap || next.dirty > next.processed ? now + minimumAlarmDelay : void 0;
|
|
651
613
|
})));
|
|
652
614
|
yield* failpoint.hit("maintenance:checkpoint:after");
|
|
653
615
|
return {
|
|
@@ -661,47 +623,17 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
|
|
|
661
623
|
const notified = (yield* Stream.toPull(wakes.wakes)).pipe(Effect.asVoid, Effect.catch(() => Effect.never));
|
|
662
624
|
const started = yield* beginNative(observed);
|
|
663
625
|
const auxiliaryScope = yield* Effect.acquireRelease(Scope.make("parallel"), (scope, exit) => Scope.close(scope, exit));
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const initialized = yield* Deferred.make();
|
|
676
|
-
let active = 0;
|
|
677
|
-
let failed = false;
|
|
678
|
-
return {
|
|
679
|
-
activity: {
|
|
680
|
-
ready: Deferred.succeed(initialized, void 0).pipe(Effect.andThen(signalActivity), Effect.asVoid, Effect.uninterruptible),
|
|
681
|
-
subscribeChanges: PubSub.subscribe(checks).pipe(Effect.map(PubSub.take)),
|
|
682
|
-
run: (wave) => Effect.acquireUseRelease(Effect.sync(() => {
|
|
683
|
-
if (!dispatchOpen) return false;
|
|
684
|
-
active++;
|
|
685
|
-
return true;
|
|
686
|
-
}), (admitted) => !admitted ? Effect.void : (allowance === void 0 ? wave : wave.pipe(Effect.timeoutOrElse({
|
|
687
|
-
duration: allowance,
|
|
688
|
-
orElse: () => DurableAlarmError.make({
|
|
689
|
-
operation: "host dispatch allowance",
|
|
690
|
-
message: "The admitted host wave exceeded its allowance; durable work remains pending"
|
|
691
|
-
})
|
|
692
|
-
}))).pipe(Effect.onExit((exit) => Effect.sync(() => {
|
|
693
|
-
if (Exit.isFailure(exit)) failed = true;
|
|
694
|
-
}))), (admitted) => Effect.sync(() => {
|
|
695
|
-
if (admitted) active--;
|
|
696
|
-
}).pipe(Effect.andThen(signalActivity)))
|
|
697
|
-
},
|
|
698
|
-
initialized: Deferred.await(initialized),
|
|
699
|
-
quiet: () => active === 0 && Deferred.isDoneUnsafe(initialized),
|
|
700
|
-
failed: () => failed
|
|
701
|
-
};
|
|
702
|
-
});
|
|
703
|
-
const messageActivity = yield* makeActivity();
|
|
704
|
-
const hostActivity = yield* makeActivity(host.dispatchTimeoutMillis);
|
|
626
|
+
const fork = (work) => Effect.forkIn(work, auxiliaryScope);
|
|
627
|
+
const lanes = host.lanes.map((lane) => ({
|
|
628
|
+
...lane,
|
|
629
|
+
initial: true,
|
|
630
|
+
check: true,
|
|
631
|
+
exhausted: false,
|
|
632
|
+
fiber: void 0
|
|
633
|
+
}));
|
|
634
|
+
let messageExhausted = false;
|
|
635
|
+
let delivery;
|
|
636
|
+
let failure;
|
|
705
637
|
const recovery = {
|
|
706
638
|
queue: yield* Deferred.make(),
|
|
707
639
|
pending: /* @__PURE__ */ new Set(),
|
|
@@ -713,7 +645,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
|
|
|
713
645
|
recovered: 0,
|
|
714
646
|
repaired: false
|
|
715
647
|
};
|
|
716
|
-
const recoveryFiber = yield*
|
|
648
|
+
const recoveryFiber = yield* fork(Effect.gen(function* () {
|
|
717
649
|
for (const threadId of yield* Deferred.await(recovery.queue)) {
|
|
718
650
|
yield* failpoint.hit("maintenance:select:before");
|
|
719
651
|
yield* runTransaction("select old recovery lane", () => ctx.storage.transaction(async (transaction) => {
|
|
@@ -728,90 +660,156 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
|
|
|
728
660
|
recovery.pending.delete(threadId);
|
|
729
661
|
yield* wakes.notify(threadId);
|
|
730
662
|
}
|
|
731
|
-
})
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
operation: "host initialization",
|
|
743
|
-
message: "The host did not account for initial maintenance before its allowance; durable work remains pending"
|
|
744
|
-
})
|
|
745
|
-
}), Effect.andThen(Effect.never));
|
|
746
|
-
yield* Effect.raceFirst(host.drainUntil(stopDispatch, dispatchUntil).pipe(Effect.provideService(ThreadMaintenanceActivity, hostActivity.activity)), initialization);
|
|
747
|
-
}).pipe(Effect.onExit(() => hostActivity.activity.ready))), auxiliaryScope);
|
|
748
|
-
const backfill = yield* Effect.forkIn(drainDue.pipe(Effect.provideService(ThreadProjectionMaintenance, projection), Effect.timeoutOption(config.projectionDispatchTimeoutMillis), Effect.onExit(() => signalActivity)), auxiliaryScope);
|
|
749
|
-
let result = yield* advance(started, yieldAfter, observed, recovery);
|
|
750
|
-
recovery.started = true;
|
|
751
|
-
yield* Deferred.succeed(recovery.queue, []);
|
|
663
|
+
}));
|
|
664
|
+
const backfill = yield* fork(drainDue.pipe(Effect.provideService(ThreadProjectionMaintenance, projection), Effect.timeoutOption(config.projectionDispatchTimeoutMillis)));
|
|
665
|
+
let backfillObserved = false;
|
|
666
|
+
let recoveryObserved = false;
|
|
667
|
+
let native = yield* fork(advance(started, yieldAfter, observed, recovery));
|
|
668
|
+
let result = {
|
|
669
|
+
phase: "caught-up",
|
|
670
|
+
settled: 0,
|
|
671
|
+
nonterminal: started.nonterminal,
|
|
672
|
+
nextAttemptAt: void 0
|
|
673
|
+
};
|
|
752
674
|
let phase = result.phase;
|
|
753
|
-
let settled =
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
}), auxiliaryScope);
|
|
761
|
-
const retired = yield* Effect.forkChild(Fiber.joinAll([
|
|
762
|
-
deliveryFiber,
|
|
763
|
-
hostJoin,
|
|
764
|
-
backfill,
|
|
765
|
-
recoveryFiber
|
|
766
|
-
]));
|
|
675
|
+
let settled = 0;
|
|
676
|
+
let nativeCheck = false;
|
|
677
|
+
const until = DateTime.toEpochMillis(yieldAfter);
|
|
678
|
+
const dispatchEnd = DateTime.toEpochMillis(dispatchUntil);
|
|
679
|
+
const checkLanes = () => {
|
|
680
|
+
for (const lane of lanes) lane.check = true;
|
|
681
|
+
};
|
|
767
682
|
while (true) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
if (
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
683
|
+
if (native?.pollUnsafe() !== void 0) {
|
|
684
|
+
result = yield* Fiber.join(native);
|
|
685
|
+
native = void 0;
|
|
686
|
+
if (result.phase === "actionable") phase = "actionable";
|
|
687
|
+
settled += result.settled;
|
|
688
|
+
observed.nativeOnly = false;
|
|
689
|
+
recovery.started = true;
|
|
690
|
+
yield* Deferred.succeed(recovery.queue, []);
|
|
691
|
+
checkLanes();
|
|
692
|
+
}
|
|
693
|
+
if (!recoveryObserved && recoveryFiber.pollUnsafe() !== void 0) {
|
|
694
|
+
yield* Fiber.join(recoveryFiber);
|
|
695
|
+
recoveryObserved = true;
|
|
696
|
+
nativeCheck = recovery.needsCheckpoint;
|
|
697
|
+
}
|
|
698
|
+
if (!backfillObserved) {
|
|
699
|
+
const exit = backfill.pollUnsafe();
|
|
700
|
+
if (exit !== void 0) {
|
|
701
|
+
backfillObserved = true;
|
|
702
|
+
if (Exit.isFailure(exit)) failure ??= exit.cause;
|
|
778
703
|
}
|
|
779
704
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
705
|
+
for (const lane of lanes) {
|
|
706
|
+
const exit = lane.fiber?.pollUnsafe();
|
|
707
|
+
if (exit === void 0) continue;
|
|
708
|
+
lane.fiber = void 0;
|
|
709
|
+
if (Exit.isFailure(exit)) {
|
|
710
|
+
failure ??= exit.cause;
|
|
711
|
+
lane.exhausted = true;
|
|
712
|
+
} else lane.check ||= exit.value;
|
|
713
|
+
}
|
|
714
|
+
const deliveryExit = delivery?.pollUnsafe();
|
|
715
|
+
if (deliveryExit !== void 0) {
|
|
716
|
+
delivery = void 0;
|
|
717
|
+
if (Exit.isFailure(deliveryExit)) {
|
|
718
|
+
failure ??= deliveryExit.cause;
|
|
719
|
+
messageExhausted = true;
|
|
720
|
+
}
|
|
793
721
|
}
|
|
722
|
+
if (failure !== void 0 && native === void 0) return yield* Effect.failCause(failure);
|
|
794
723
|
const now = yield* Clock.currentTimeMillis;
|
|
795
|
-
const until = DateTime.toEpochMillis(yieldAfter);
|
|
796
724
|
if (now >= until) break;
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
725
|
+
for (const lane of lanes) {
|
|
726
|
+
if (lane.fiber !== void 0 || lane.exhausted || !lane.check) continue;
|
|
727
|
+
lane.check = false;
|
|
728
|
+
lane.fiber = yield* fork(Effect.gen(function* () {
|
|
729
|
+
yield* Schema.decodeEffect(AuxiliaryDispatchMillis)(lane.dispatchTimeoutMillis).pipe(Effect.mapError((cause) => DurableAlarmError.make({
|
|
730
|
+
operation: "host dispatch allowance",
|
|
731
|
+
message: "Declare an integer whole-wave allowance between 1 and 300000 milliseconds",
|
|
732
|
+
cause
|
|
733
|
+
})));
|
|
734
|
+
if ((yield* Clock.currentTimeMillis) + lane.dispatchTimeoutMillis > dispatchEnd) {
|
|
735
|
+
lane.exhausted = true;
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
return yield* Effect.gen(function* () {
|
|
739
|
+
if (!lane.initial) {
|
|
740
|
+
const deadline = yield* lane.pendingDeadline;
|
|
741
|
+
if (Option.isNone(deadline) || deadline.value > (yield* Clock.currentTimeMillis)) return false;
|
|
742
|
+
}
|
|
743
|
+
const selectedAt = yield* Clock.currentTimeMillis;
|
|
744
|
+
if (selectedAt >= until || selectedAt + lane.dispatchTimeoutMillis > dispatchEnd) {
|
|
745
|
+
lane.exhausted = true;
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
lane.initial = false;
|
|
749
|
+
yield* lane.run;
|
|
750
|
+
return true;
|
|
751
|
+
}).pipe(Effect.scoped, Effect.timeoutOrElse({
|
|
752
|
+
duration: lane.dispatchTimeoutMillis,
|
|
753
|
+
orElse: () => DurableAlarmError.make({
|
|
754
|
+
operation: "host dispatch allowance",
|
|
755
|
+
message: "The admitted host wave exceeded its allowance; durable work remains pending"
|
|
756
|
+
})
|
|
757
|
+
}));
|
|
758
|
+
}));
|
|
759
|
+
}
|
|
760
|
+
if (delivery === void 0 && !messageExhausted) {
|
|
761
|
+
const selected = yield* Effect.gen(function* () {
|
|
762
|
+
const deadline = yield* messages.pendingDeadline;
|
|
763
|
+
return Option.isSome(deadline) && deadline.value <= (yield* Clock.currentTimeMillis) ? Option.some(yield* messages.prepare) : Option.none();
|
|
764
|
+
}).pipe(Effect.exit);
|
|
765
|
+
if (Exit.isFailure(selected)) {
|
|
766
|
+
failure ??= selected.cause;
|
|
767
|
+
messageExhausted = true;
|
|
768
|
+
} else if (Option.isSome(selected.value)) {
|
|
769
|
+
const wave = selected.value.value;
|
|
770
|
+
const selectedAt = yield* Clock.currentTimeMillis;
|
|
771
|
+
if (selectedAt >= until || selectedAt + wave.timeoutMillis > dispatchEnd) messageExhausted = true;
|
|
772
|
+
else delivery = yield* fork(wave.run);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (native === void 0 && recoveryObserved && !recovery.needsCheckpoint && backfillObserved && delivery === void 0 && lanes.every((lane) => lane.fiber === void 0)) break;
|
|
776
|
+
if (native === void 0 && nativeCheck) {
|
|
777
|
+
nativeCheck = false;
|
|
778
|
+
const checkpoint = recoveryObserved && recovery.needsCheckpoint;
|
|
779
|
+
if (checkpoint) recovery.needsCheckpoint = false;
|
|
780
|
+
native = yield* fork(Effect.gen(function* () {
|
|
781
|
+
const awakened = checkpoint ? started : yield* beginNative(observed);
|
|
782
|
+
return yield* advance(awakened, yieldAfter, observed, recovery, !checkpoint || settled === 0);
|
|
783
|
+
}));
|
|
784
|
+
}
|
|
785
|
+
const next = Math.min(native === void 0 ? result.nextAttemptAt ?? Infinity : Infinity, now + config.wakeScanInterval, until);
|
|
786
|
+
const completing = [
|
|
787
|
+
...native === void 0 ? [] : [native],
|
|
788
|
+
...recoveryObserved ? [] : [recoveryFiber],
|
|
789
|
+
...backfillObserved ? [] : [backfill],
|
|
790
|
+
...delivery === void 0 ? [] : [delivery],
|
|
791
|
+
...lanes.flatMap((lane) => lane.fiber === void 0 ? [] : [lane.fiber])
|
|
792
|
+
];
|
|
793
|
+
if ((yield* Effect.raceAllFirst([...completing.map((fiber) => Fiber.await(fiber).pipe(Effect.as("completed"))), Effect.raceFirst(notified, Effect.sleep(Math.max(0, next - now))).pipe(Effect.as("scan"))])) === "scan") {
|
|
794
|
+
nativeCheck = true;
|
|
795
|
+
checkLanes();
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (native !== void 0) {
|
|
799
|
+
result = yield* Fiber.join(native);
|
|
808
800
|
if (result.phase === "actionable") phase = "actionable";
|
|
809
801
|
settled += result.settled;
|
|
810
802
|
observed.nativeOnly = false;
|
|
811
|
-
|
|
803
|
+
recovery.started = true;
|
|
804
|
+
yield* Deferred.succeed(recovery.queue, []);
|
|
812
805
|
}
|
|
813
|
-
yield*
|
|
814
|
-
|
|
806
|
+
yield* Fiber.joinAll([
|
|
807
|
+
recoveryFiber,
|
|
808
|
+
backfill,
|
|
809
|
+
...delivery === void 0 ? [] : [delivery],
|
|
810
|
+
...lanes.flatMap((lane) => lane.fiber === void 0 ? [] : [lane.fiber])
|
|
811
|
+
]);
|
|
812
|
+
if (failure !== void 0) return yield* Effect.failCause(failure);
|
|
815
813
|
if (recovery.needsCheckpoint) {
|
|
816
814
|
result = yield* advance(started, yieldAfter, observed, recovery, false);
|
|
817
815
|
if (result.phase === "actionable") phase = "actionable";
|
|
@@ -872,6 +870,6 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
|
|
|
872
870
|
}));
|
|
873
871
|
};
|
|
874
872
|
//#endregion
|
|
875
|
-
export { DurableAlarmError, DurableAlarmService, MaintenancePassReport, ThreadHostMaintenance, ThreadMaintenance,
|
|
873
|
+
export { DurableAlarmError, DurableAlarmService, MaintenancePassReport, ThreadHostMaintenance, ThreadMaintenance, ThreadMaintenanceFailpoint, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadRecoveryFault, publishCommitted, Alarm_exports as t };
|
|
876
874
|
|
|
877
875
|
//# sourceMappingURL=Alarm.mjs.map
|