@effect-agent/platform-cloudflare 0.1.0-beta.95 → 0.1.0-beta.96
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 +3 -3
- package/dist/Alarm.mjs +141 -51
- package/dist/Alarm.mjs.map +1 -1
- package/dist/{CloudflareConfig-D472JOQA.d.mts → CloudflareConfig-DKXGo8L3.d.mts} +5 -5
- package/dist/CloudflareConfig.d.mts +1 -1
- package/dist/CloudflareConfig.mjs +4 -4
- package/dist/CloudflareConfig.mjs.map +1 -1
- package/dist/CloudflareThreadClient.d.mts +85 -16
- package/dist/ThreadObject-DXavwaU-.mjs.map +1 -1
- package/dist/{ThreadObject-CZxuDWOb.d.mts → ThreadObject-DiqVDdJE.d.mts} +5 -5
- package/dist/ThreadObject.d.mts +1 -1
- package/dist/index.d.mts +2 -2
- package/package.json +1 -1
- package/src/Alarm.ts +285 -67
- package/src/CloudflareConfig.ts +4 -4
- package/src/internal/layers.ts +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DurableObjectContext, ThreadObjectIdentity, ThreadObjectNamespace, ThreadObjectPlacement } from "./CloudflareBindings.mjs";
|
|
2
|
-
import { l as CloudflarePlatformConfigError, s as CloudflareDurableRuntimeConfig, t as AdmissionLimitExceeded } from "./CloudflareConfig-
|
|
2
|
+
import { l as CloudflarePlatformConfigError, s as CloudflareDurableRuntimeConfig, t as AdmissionLimitExceeded } from "./CloudflareConfig-DKXGo8L3.mjs";
|
|
3
3
|
import { DurableAlarmError, DurableAlarmService, ThreadMaintenance, ThreadMaintenanceFailpoint, ThreadMaintenanceFailpointHandler, ThreadMutationGate, ThreadPublication } from "./Alarm.mjs";
|
|
4
4
|
import { HostProtocolError, SubmitRequest } from "./CloudflareThreadClient.mjs";
|
|
5
5
|
import { Context, Crypto, Effect, Layer, Option, Schema, Scope } from "effect";
|
|
@@ -55,9 +55,9 @@ interface CloudflareDurableRuntimeOptions {
|
|
|
55
55
|
readonly ownershipLeaseDuration?: number | undefined;
|
|
56
56
|
/** Milliseconds; default 100. */
|
|
57
57
|
readonly alarmBackoffBase?: number | undefined;
|
|
58
|
-
/**
|
|
58
|
+
/** Failed/no-progress exponential backoff ceiling in milliseconds; default 5000. */
|
|
59
59
|
readonly alarmBackoffCap?: number | undefined;
|
|
60
|
-
/**
|
|
60
|
+
/** Fallback scan cadence for newly dirty work in milliseconds; default 1000. */
|
|
61
61
|
readonly wakeScanInterval?: number | undefined;
|
|
62
62
|
/** Milliseconds; default 500. */
|
|
63
63
|
readonly settlementPollInterval?: number | undefined;
|
|
@@ -721,7 +721,7 @@ declare const encodeAdminResponse: (input: AdminFailed | ExplainedRecovery | Obl
|
|
|
721
721
|
readonly cause?: Schema.Json | undefined;
|
|
722
722
|
} | {
|
|
723
723
|
readonly _tag: "DurableRuntimeFailpointError";
|
|
724
|
-
readonly location: "abort:after-intent" | "approval:after-request-append" | "approval:after-suspend" | "checkpoint:after-save" | "checkpoint:before-save" | "claim:after-claim" | "compaction:after-canonical-append" | "compaction:before-canonical-append" | "input:after-canonical-append" | "join:after-canonical-append" | "join:after-claim" | "policy:after-reservation-append" | "policy:before-reservation-append" | "resolve:after-intent" | "run:after-start-append" | "run:before-start-append" | "step:after-step-append" | "subagent:after-admit" | "subagent:after-child-abort-intent" | "subagent:after-child-ready" | "subagent:after-join-append" | "subagent:after-release" | "subagent:after-release-pending" | "subagent:after-request-append" | "subagent:after-reserve" | "subagent:after-sibling-settle" | "subagent:after-start-append" | "subagent:after-suspend" | "subagent:before-join-append" | "submit:after-admit" | "submit:after-materialize" | "terminalize:after-canonical-append" | "terminalize:after-reserve" | "tools:after-prepared-append" | "tools:before-prepared-append" | "turn:after-canonical-append" | "turn:after-response-append" | "turn:after-results-append" | "update:after-canonical-append" | "update:after-delivery-insert" | "update:before-canonical-append" | "update:before-delivery-insert" | "worker:after-completion-append" | "worker:after-origin-append" | "worker:after-report-append" | "worker:after-report-delivery" | "worker:after-source-append" | "worker:after-subtree-append" | "worker:before-completion-append" | "worker:before-origin-append" | "worker:before-report-append" | "worker:before-report-delivery" | "worker:before-source-append" | "worker:before-subtree-append";
|
|
724
|
+
readonly location: "abort:after-intent" | "approval:after-request-append" | "approval:after-suspend" | "checkpoint:after-save" | "checkpoint:before-save" | "claim:after-claim" | "compaction:after-canonical-append" | "compaction:before-canonical-append" | "input:after-canonical-append" | "join:after-canonical-append" | "join:after-claim" | "policy:after-reservation-append" | "policy:before-reservation-append" | "resolve:after-intent" | "run:after-duration-append" | "run:after-start-append" | "run:before-duration-append" | "run:before-start-append" | "step:after-step-append" | "subagent:after-admit" | "subagent:after-child-abort-intent" | "subagent:after-child-ready" | "subagent:after-join-append" | "subagent:after-release" | "subagent:after-release-pending" | "subagent:after-request-append" | "subagent:after-reserve" | "subagent:after-sibling-settle" | "subagent:after-start-append" | "subagent:after-suspend" | "subagent:before-join-append" | "submit:after-admit" | "submit:after-materialize" | "terminalize:after-canonical-append" | "terminalize:after-reserve" | "tools:after-prepared-append" | "tools:before-prepared-append" | "turn:after-canonical-append" | "turn:after-response-append" | "turn:after-results-append" | "update:after-canonical-append" | "update:after-delivery-insert" | "update:before-canonical-append" | "update:before-delivery-insert" | "worker:after-completion-append" | "worker:after-origin-append" | "worker:after-report-append" | "worker:after-report-delivery" | "worker:after-source-append" | "worker:after-subtree-append" | "worker:before-completion-append" | "worker:before-origin-append" | "worker:before-report-append" | "worker:before-report-delivery" | "worker:before-source-append" | "worker:before-subtree-append";
|
|
725
725
|
} | {
|
|
726
726
|
readonly _tag: "FenceRejected";
|
|
727
727
|
readonly threadId: string;
|
|
@@ -811,4 +811,4 @@ interface Class<EventServices = never> {
|
|
|
811
811
|
declare const make: <ApplicationServices, ApplicationError, EventServices = never, EventLayerError = never>(applicationLayer: Layer.Layer<CloudflareDurableRuntimeServices | ApplicationServices, ApplicationError, CloudflareBootstrapServices | DurableObjectState$1.DurableObjectState | WorkerEnvironment | DurableObjectContext | ThreadObjectNamespace>, options: Options<ApplicationServices, EventServices, EventLayerError>) => Class<ApplicationServices | EventServices>;
|
|
812
812
|
//#endregion
|
|
813
813
|
export { ThreadPublicationOptions as A, portCall as C, CloudflareDurableRuntimeOptions as D, CloudflareDurableRuntimeInitializationError as E, layerConfig as M, layerHostConfig as N, CloudflareDurableRuntimeServices as O, layerInHost as P, make as S, CloudflareBootstrapServices as T, decodeAdminVerifyRequest as _, AdminVerifyRequest as a, encodeAdminResponse as b, Instance as c, RetryExecuted as d, ThreadObject_d_exports as f, decodeAdminResponse as g, decodeAdminExplainRequest as h, AdminResponse as i, layer as j, ThreadObjectPorts as k, ObligationsScanned as l, VerifiedIntegrity as m, AdminFailed as n, Class as o, ThreadRpcOperation as p, AdminFailure as r, ExplainedRecovery as s, AdminExplainRequest as t, Options as u, decodeObligationThresholds as v, submit as w, handleRpc as x, decodeRetryCommand as y };
|
|
814
|
-
//# sourceMappingURL=ThreadObject-
|
|
814
|
+
//# sourceMappingURL=ThreadObject-DiqVDdJE.d.mts.map
|
package/dist/ThreadObject.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as ThreadPublicationOptions, C as portCall, D as CloudflareDurableRuntimeOptions, E as CloudflareDurableRuntimeInitializationError, M as layerConfig, N as layerHostConfig, O as CloudflareDurableRuntimeServices, P as layerInHost, S as make, T as CloudflareBootstrapServices, _ as decodeAdminVerifyRequest, a as AdminVerifyRequest, b as encodeAdminResponse, c as Instance, d as RetryExecuted, g as decodeAdminResponse, h as decodeAdminExplainRequest, i as AdminResponse, j as layer, k as ThreadObjectPorts, l as ObligationsScanned, m as VerifiedIntegrity, n as AdminFailed, o as Class, p as ThreadRpcOperation, r as AdminFailure, s as ExplainedRecovery, t as AdminExplainRequest, u as Options, v as decodeObligationThresholds, w as submit, x as handleRpc, y as decodeRetryCommand } from "./ThreadObject-
|
|
1
|
+
import { A as ThreadPublicationOptions, C as portCall, D as CloudflareDurableRuntimeOptions, E as CloudflareDurableRuntimeInitializationError, M as layerConfig, N as layerHostConfig, O as CloudflareDurableRuntimeServices, P as layerInHost, S as make, T as CloudflareBootstrapServices, _ as decodeAdminVerifyRequest, a as AdminVerifyRequest, b as encodeAdminResponse, c as Instance, d as RetryExecuted, g as decodeAdminResponse, h as decodeAdminExplainRequest, i as AdminResponse, j as layer, k as ThreadObjectPorts, l as ObligationsScanned, m as VerifiedIntegrity, n as AdminFailed, o as Class, p as ThreadRpcOperation, r as AdminFailure, s as ExplainedRecovery, t as AdminExplainRequest, u as Options, v as decodeObligationThresholds, w as submit, x as handleRpc, y as decodeRetryCommand } from "./ThreadObject-DiqVDdJE.mjs";
|
|
2
2
|
export { AdminExplainRequest, AdminFailed, AdminFailure, AdminResponse, AdminVerifyRequest, type CloudflareBootstrapServices as BootstrapServices, Class, ExplainedRecovery, type CloudflareDurableRuntimeInitializationError as InitializationError, Instance, ObligationsScanned, Options, type ThreadPublicationOptions as PublicationOptions, RetryExecuted, type CloudflareDurableRuntimeOptions as RuntimeOptions, type CloudflareDurableRuntimeServices as Services, ThreadObjectPorts, ThreadRpcOperation, VerifiedIntegrity, decodeAdminExplainRequest, decodeAdminResponse, decodeAdminVerifyRequest, decodeObligationThresholds, decodeRetryCommand, encodeAdminResponse, handleRpc, layer, layerConfig, layerHostConfig, layerInHost, make, portCall, submit };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as CloudflareBindings_d_exports } from "./CloudflareBindings.mjs";
|
|
2
|
-
import { o as CloudflareConfig_d_exports } from "./CloudflareConfig-
|
|
2
|
+
import { o as CloudflareConfig_d_exports } from "./CloudflareConfig-DKXGo8L3.mjs";
|
|
3
3
|
import { t as Alarm_d_exports } from "./Alarm.mjs";
|
|
4
4
|
import { t as CloudflareBrowser_d_exports } from "./CloudflareBrowser-Doe1M7R5.mjs";
|
|
5
5
|
import { t as CloudflareAiGateway_d_exports } from "./CloudflareAiGateway.mjs";
|
|
@@ -8,6 +8,6 @@ import { t as CloudflareMemory_d_exports } from "./CloudflareMemory.mjs";
|
|
|
8
8
|
import { t as CloudflareScheduling_d_exports } from "./CloudflareScheduling.mjs";
|
|
9
9
|
import { t as CloudflareSubscriptions_d_exports } from "./CloudflareSubscriptions.mjs";
|
|
10
10
|
import { t as CloudflareThreadClient_d_exports } from "./CloudflareThreadClient.mjs";
|
|
11
|
-
import { f as ThreadObject_d_exports } from "./ThreadObject-
|
|
11
|
+
import { f as ThreadObject_d_exports } from "./ThreadObject-DiqVDdJE.mjs";
|
|
12
12
|
import { t as WakeScheduler_d_exports } from "./WakeScheduler.mjs";
|
|
13
13
|
export { Alarm_d_exports as Alarm, CloudflareAiGateway_d_exports as CloudflareAiGateway, CloudflareBindings_d_exports as CloudflareBindings, CloudflareBrowser_d_exports as CloudflareBrowser, CloudflareCodeMode_d_exports as CloudflareCodeMode, CloudflareConfig_d_exports as CloudflareConfig, CloudflareMemory_d_exports as CloudflareMemory, CloudflareScheduling_d_exports as CloudflareScheduling, CloudflareSubscriptions_d_exports as CloudflareSubscriptions, CloudflareThreadClient_d_exports as CloudflareThreadClient, ThreadObject_d_exports as ThreadObject, WakeScheduler_d_exports as WakeScheduler };
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@effect-agent/platform-cloudflare","version":"0.1.0-beta.
|
|
1
|
+
{"name":"@effect-agent/platform-cloudflare","version":"0.1.0-beta.96","dependencies":{"@effect-agent/storage-cloudflare":"0.1.0-beta.96","@effect/platform-browser":"4.0.0-rc.115","@effect/sql-sqlite-do":"4.0.0-rc.115","effect-agent":"0.1.0-beta.96"},"devDependencies":{"@cloudflare/puppeteer":"1.1.0","@cloudflare/vitest-pool-workers":"0.21.3","@cloudflare/workers-types":"5.20260825.1","@effect-agent/testing":"0.1.0-beta.96","@effect/platform-node":"4.0.0-rc.115","@effect/sql-d1":"4.0.0-rc.115","@effect/vitest":"4.0.0-rc.115","effect":"4.0.0-rc.115","effect-cf":"0.44.1","esbuild":"0.28.1","miniflare":"5.20260811.1-alpha","typescript":"7.0.2","vite-plus":"0.3.0","vitest":"4.1.11"},"peerDependencies":{"@cloudflare/puppeteer":"^1.1.0","effect":"^4.0.0-rc.115","effect-cf":"^0.44.1"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./alarm":{"types":"./dist/Alarm.d.mts","default":"./dist/Alarm.mjs"},"./browser-rest-capture":{"types":"./dist/BrowserRestCapture.d.mts","default":"./dist/BrowserRestCapture.mjs"},"./browser-rest-crawl":{"types":"./dist/BrowserRestCrawl.d.mts","default":"./dist/BrowserRestCrawl.mjs"},"./cloudflare-bindings":{"types":"./dist/CloudflareBindings.d.mts","default":"./dist/CloudflareBindings.mjs"},"./cloudflare-browser":{"types":"./dist/CloudflareBrowser.d.mts","default":"./dist/CloudflareBrowser.mjs"},"./cloudflare-code-mode":{"types":"./dist/CloudflareCodeMode.d.mts","default":"./dist/CloudflareCodeMode.mjs"},"./cloudflare-config":{"types":"./dist/CloudflareConfig.d.mts","default":"./dist/CloudflareConfig.mjs"},"./cloudflare-memory":{"types":"./dist/CloudflareMemory.d.mts","default":"./dist/CloudflareMemory.mjs"},"./cloudflare-scheduling":{"types":"./dist/CloudflareScheduling.d.mts","default":"./dist/CloudflareScheduling.mjs"},"./cloudflare-subscriptions":{"types":"./dist/CloudflareSubscriptions.d.mts","default":"./dist/CloudflareSubscriptions.mjs"},"./cloudflare-thread-client":{"types":"./dist/CloudflareThreadClient.d.mts","default":"./dist/CloudflareThreadClient.mjs"},"./interactive-browser":{"types":"./dist/InteractiveBrowser.d.mts","default":"./dist/InteractiveBrowser.mjs"},"./protected-browser":{"types":"./dist/ProtectedBrowser.d.mts","default":"./dist/ProtectedBrowser.mjs"},"./thread-object":{"types":"./dist/ThreadObject.d.mts","default":"./dist/ThreadObject.mjs"},"./wake-scheduler":{"types":"./dist/WakeScheduler.d.mts","default":"./dist/WakeScheduler.mjs"},"./cloudflare-ai-gateway":{"types":"./dist/CloudflareAiGateway.d.mts","default":"./dist/CloudflareAiGateway.mjs"}},"description":"Cloudflare Layer assembly for Effect Agent: Durable Objects and Browser Run adapters.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-cloudflare"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json"},"peerDependenciesMeta":{"@cloudflare/puppeteer":{"optional":true}}}
|
package/src/Alarm.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Scope,
|
|
16
16
|
Semaphore,
|
|
17
17
|
Stream,
|
|
18
|
+
Struct,
|
|
18
19
|
} from "effect";
|
|
19
20
|
import { type DurableBindingFailure } from "effect-agent/agent-registration";
|
|
20
21
|
import {
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
type DurableWorkerFailure,
|
|
23
24
|
type RecoveryReport,
|
|
24
25
|
} from "effect-agent/durable-agent-runtime";
|
|
25
|
-
import { ThreadId } from "effect-agent/identifiers";
|
|
26
|
+
import { ThreadId, SubmissionId } from "effect-agent/identifiers";
|
|
26
27
|
import { SubmissionLedger, type SubmissionSnapshot } from "effect-agent/submission-ledger";
|
|
27
28
|
import {
|
|
28
29
|
ThreadProjectionMaintenance,
|
|
@@ -207,6 +208,10 @@ export type ThreadMaintenanceFailpointLocation =
|
|
|
207
208
|
| "maintenance:begin:after"
|
|
208
209
|
| "maintenance:select:before"
|
|
209
210
|
| "maintenance:select:after"
|
|
211
|
+
| "maintenance:binding-retry:before"
|
|
212
|
+
| "maintenance:binding-retry:after"
|
|
213
|
+
| "maintenance:retry:before"
|
|
214
|
+
| "maintenance:retry:after"
|
|
210
215
|
| "maintenance:finish:before"
|
|
211
216
|
| "maintenance:finish:after";
|
|
212
217
|
|
|
@@ -338,6 +343,26 @@ const MaintenanceGeneration = Schema.BigIntFromString.check(
|
|
|
338
343
|
Schema.isGreaterThanOrEqualToBigInt(0n),
|
|
339
344
|
);
|
|
340
345
|
|
|
346
|
+
class BindingRetry extends Schema.Class<BindingRetry>("BindingRetry")({
|
|
347
|
+
threadId: ThreadId,
|
|
348
|
+
submissionId: SubmissionId,
|
|
349
|
+
attempts: Schema.Natural,
|
|
350
|
+
notBefore: Schema.Finite,
|
|
351
|
+
reportedAt: Schema.Finite,
|
|
352
|
+
}) {}
|
|
353
|
+
|
|
354
|
+
interface MaintenanceObservation {
|
|
355
|
+
generation?: bigint;
|
|
356
|
+
nativeOnly: boolean;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
class MaintenanceRetry extends Schema.Class<MaintenanceRetry>("MaintenanceRetry")({
|
|
360
|
+
generation: MaintenanceGeneration,
|
|
361
|
+
notBefore: Schema.Finite,
|
|
362
|
+
nativeOnly: Schema.Boolean,
|
|
363
|
+
stalls: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(30)),
|
|
364
|
+
}) {}
|
|
365
|
+
|
|
341
366
|
/** Versioned, platform-private maintenance state stored through Durable Object KV. */
|
|
342
367
|
class ThreadMaintenanceState extends Schema.Class<ThreadMaintenanceState>(
|
|
343
368
|
"@effect-agent/platform-cloudflare/ThreadMaintenanceState",
|
|
@@ -348,6 +373,9 @@ class ThreadMaintenanceState extends Schema.Class<ThreadMaintenanceState>(
|
|
|
348
373
|
nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
349
374
|
/** One physical-owner cursor; old single-lane records need no conversion. */
|
|
350
375
|
lastServedThreadId: Schema.optionalKey(ThreadId),
|
|
376
|
+
bindingRetries: Schema.optionalKey(Schema.Array(BindingRetry)),
|
|
377
|
+
/** Absent on older records. A newer mutation makes this retry obsolete. */
|
|
378
|
+
retry: Schema.optionalKey(MaintenanceRetry),
|
|
351
379
|
}) {}
|
|
352
380
|
|
|
353
381
|
const MAINTENANCE_STATE_KEY = "effect-agent:thread-maintenance:v1";
|
|
@@ -525,7 +553,7 @@ export type MaintenancePassFailure =
|
|
|
525
553
|
export class ThreadMaintenance extends Context.Service<
|
|
526
554
|
ThreadMaintenance,
|
|
527
555
|
{
|
|
528
|
-
/** One idempotent
|
|
556
|
+
/** One idempotent pass; failures propagate after durably scheduling bounded recovery. */
|
|
529
557
|
readonly pass: Effect.Effect<MaintenancePassReport, MaintenancePassFailure>;
|
|
530
558
|
/**
|
|
531
559
|
* Constructor gate: initialize/inspect only the O(1) maintenance record and ensure a dirty
|
|
@@ -564,11 +592,6 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
564
592
|
const { ctx } = yield* DurableObjectContext;
|
|
565
593
|
const failpoint = yield* ThreadMaintenanceFailpoint;
|
|
566
594
|
|
|
567
|
-
/**
|
|
568
|
-
* Consecutive no-progress passes — an in-memory CACHE, not state: a fresh incarnation
|
|
569
|
-
* restarts at zero and merely re-arms sooner than a long-lived one would have.
|
|
570
|
-
*/
|
|
571
|
-
const stalls = yield* Ref.make(0);
|
|
572
595
|
const mutations = yield* ThreadMutationGate;
|
|
573
596
|
const publication = yield* ThreadPublication;
|
|
574
597
|
const projection = yield* ThreadProjectionMaintenance;
|
|
@@ -602,7 +625,7 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
602
625
|
yield* failpoint.hit("maintenance:ensure:before");
|
|
603
626
|
const now = yield* Clock.currentTimeMillis;
|
|
604
627
|
|
|
605
|
-
yield* runTransaction("ensure maintenance alarm", () =>
|
|
628
|
+
const retry = yield* runTransaction("ensure maintenance alarm", () =>
|
|
606
629
|
ctx.storage.transaction(async (transaction) => {
|
|
607
630
|
const { state, initialized } = await readMaintenanceState(transaction);
|
|
608
631
|
|
|
@@ -610,10 +633,18 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
610
633
|
await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));
|
|
611
634
|
}
|
|
612
635
|
if (state.dirty > state.processed) {
|
|
613
|
-
await ensureTransactionAlarmBy(
|
|
636
|
+
await ensureTransactionAlarmBy(
|
|
637
|
+
transaction,
|
|
638
|
+
state.retry?.generation === state.dirty
|
|
639
|
+
? Math.max(now + minimumAlarmDelay, state.retry.notBefore)
|
|
640
|
+
: now + config.wakeScanInterval,
|
|
641
|
+
);
|
|
614
642
|
}
|
|
643
|
+
|
|
644
|
+
return state.retry?.generation === state.dirty ? state.retry : undefined;
|
|
615
645
|
}),
|
|
616
646
|
);
|
|
647
|
+
|
|
617
648
|
const deadline = yield* pendingDeadline;
|
|
618
649
|
|
|
619
650
|
if (Option.isSome(deadline)) {
|
|
@@ -621,7 +652,12 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
621
652
|
ctx.storage.transaction((transaction) =>
|
|
622
653
|
ensureTransactionAlarmBy(
|
|
623
654
|
transaction,
|
|
624
|
-
Math.max(
|
|
655
|
+
Math.max(
|
|
656
|
+
now + minimumAlarmDelay,
|
|
657
|
+
deadline.value <= now && retry !== undefined && !retry.nativeOnly
|
|
658
|
+
? Math.max(deadline.value, retry.notBefore)
|
|
659
|
+
: deadline.value,
|
|
660
|
+
),
|
|
625
661
|
),
|
|
626
662
|
),
|
|
627
663
|
);
|
|
@@ -629,7 +665,9 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
629
665
|
yield* failpoint.hit("maintenance:ensure:after");
|
|
630
666
|
});
|
|
631
667
|
|
|
632
|
-
const beginPass = Effect.fn("ThreadMaintenance.beginPass")(function* (
|
|
668
|
+
const beginPass = Effect.fn("ThreadMaintenance.beginPass")(function* (observed: {
|
|
669
|
+
generation?: bigint;
|
|
670
|
+
}) {
|
|
633
671
|
yield* failpoint.hit("maintenance:begin:before");
|
|
634
672
|
const now = yield* Clock.currentTimeMillis;
|
|
635
673
|
|
|
@@ -637,26 +675,27 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
637
675
|
ctx.storage.transaction(async (transaction) => {
|
|
638
676
|
const { state, initialized } = await readMaintenanceState(transaction);
|
|
639
677
|
|
|
678
|
+
observed.generation = state.dirty;
|
|
640
679
|
if (!initialized) {
|
|
641
680
|
await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));
|
|
642
681
|
}
|
|
643
|
-
|
|
644
|
-
// Prearm even a publication-only pass before invoking any host hook.
|
|
645
|
-
await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);
|
|
682
|
+
const retryAt = state.retry?.generation === state.dirty ? state.retry.notBefore : 0;
|
|
646
683
|
|
|
684
|
+
// Prearm before recovery or any host hook, including publication-only passes.
|
|
685
|
+
// A completed pass may move this crash fallback later to its bounded retry.
|
|
686
|
+
await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);
|
|
687
|
+
if (state.processed >= state.dirty || retryAt > now) {
|
|
647
688
|
return {
|
|
648
689
|
_tag: "CaughtUp" as const,
|
|
649
690
|
nonterminal: state.nonterminal,
|
|
650
691
|
};
|
|
651
692
|
}
|
|
652
|
-
// Pre-arm the earliest retry before recovery. A successful finish may move this slot
|
|
653
|
-
// LATER to its bounded backoff, which does not cancel the running handler.
|
|
654
|
-
await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);
|
|
655
693
|
|
|
656
694
|
return {
|
|
657
695
|
_tag: "Actionable" as const,
|
|
658
696
|
generation: state.dirty,
|
|
659
697
|
nonterminal: state.nonterminal,
|
|
698
|
+
stalls: state.retry?.generation === state.dirty ? state.retry.stalls : 0,
|
|
660
699
|
};
|
|
661
700
|
}),
|
|
662
701
|
);
|
|
@@ -666,25 +705,88 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
666
705
|
return result;
|
|
667
706
|
});
|
|
668
707
|
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
|
|
708
|
+
const backoffDelay = (priorStalls: number, jitter: number) => {
|
|
709
|
+
const backoff = Math.min(
|
|
710
|
+
config.alarmBackoffCap,
|
|
711
|
+
config.alarmBackoffBase * 2 ** Math.min(priorStalls, 30),
|
|
672
712
|
);
|
|
673
713
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const jitter = yield* Random.next;
|
|
678
|
-
// Full jitter over [backoff/2, backoff]: desynchronizes retry storms without ever
|
|
679
|
-
// waiting longer than the deterministic bound.
|
|
680
|
-
const jittered = Math.ceil(backoff / 2 + (backoff / 2) * jitter);
|
|
714
|
+
// Jitter over [backoff/2, backoff] spreads retries without exceeding the cap.
|
|
715
|
+
return Math.ceil(backoff / 2 + (backoff / 2) * jitter);
|
|
716
|
+
};
|
|
681
717
|
|
|
682
|
-
|
|
718
|
+
const rearmDelay = Effect.fn("ThreadMaintenance.rearmDelay")(function* (
|
|
719
|
+
progressed: boolean,
|
|
720
|
+
priorStalls: number,
|
|
721
|
+
) {
|
|
722
|
+
return progressed ? config.alarmBackoffBase : backoffDelay(priorStalls, yield* Random.next);
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
const rearmFailure = Effect.fn("ThreadMaintenance.rearmFailure")(function* (
|
|
726
|
+
observed: MaintenanceObservation,
|
|
727
|
+
) {
|
|
728
|
+
const { generation, nativeOnly } = observed;
|
|
729
|
+
|
|
730
|
+
if (generation === undefined) return;
|
|
731
|
+
yield* failpoint.hit("maintenance:retry:before");
|
|
732
|
+
yield* mutations.withSnapshot((active) =>
|
|
733
|
+
Effect.gen(function* () {
|
|
734
|
+
// A failed deadline read must not prevent committing the native retry.
|
|
735
|
+
const deadline = yield* pendingDeadline.pipe(
|
|
736
|
+
Effect.catchCause(() => Effect.succeed(Option.none<number>())),
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
const now = yield* Clock.currentTimeMillis;
|
|
740
|
+
|
|
741
|
+
const jitter = yield* Random.next;
|
|
742
|
+
|
|
743
|
+
yield* runTransaction("back off failed maintenance", () =>
|
|
744
|
+
ctx.storage.transaction(async (transaction) => {
|
|
745
|
+
const { state } = await readMaintenanceState(transaction);
|
|
746
|
+
|
|
747
|
+
const previous = state.retry?.generation === generation ? state.retry : undefined;
|
|
748
|
+
|
|
749
|
+
const retry = MaintenanceRetry.make({
|
|
750
|
+
generation,
|
|
751
|
+
notBefore: Math.max(
|
|
752
|
+
previous?.notBefore ?? 0,
|
|
753
|
+
now + backoffDelay(previous?.stalls ?? 0, jitter),
|
|
754
|
+
),
|
|
755
|
+
nativeOnly,
|
|
756
|
+
stalls: Math.min(30, (previous?.stalls ?? 0) + 1),
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
await transaction.put(
|
|
760
|
+
MAINTENANCE_STATE_KEY,
|
|
761
|
+
encodeMaintenanceState(ThreadMaintenanceState.make({ ...state, retry })),
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
// Never postpone a producer that raced the failed observation. Host work
|
|
765
|
+
// retains its own deadline; an early delivery skips native recovery below.
|
|
766
|
+
const nativeDeadline =
|
|
767
|
+
active > 0 || state.dirty !== generation
|
|
768
|
+
? now + minimumAlarmDelay
|
|
769
|
+
: retry.notBefore;
|
|
770
|
+
|
|
771
|
+
await transaction.setAlarm(
|
|
772
|
+
Math.max(
|
|
773
|
+
now + minimumAlarmDelay,
|
|
774
|
+
Option.isSome(deadline) && (nativeOnly || deadline.value > now)
|
|
775
|
+
? Math.min(nativeDeadline, deadline.value)
|
|
776
|
+
: nativeDeadline,
|
|
777
|
+
),
|
|
778
|
+
);
|
|
779
|
+
}),
|
|
780
|
+
);
|
|
781
|
+
}),
|
|
782
|
+
);
|
|
783
|
+
yield* failpoint.hit("maintenance:retry:after");
|
|
683
784
|
});
|
|
684
785
|
|
|
685
786
|
const pass = Effect.fn("ThreadMaintenance.pass")(function* (
|
|
686
787
|
yieldAfter: DateTime.Utc,
|
|
687
788
|
dispatchUntil: DateTime.Utc,
|
|
789
|
+
observed: MaintenanceObservation,
|
|
688
790
|
): Effect.fn.Return<MaintenancePassReport, MaintenancePassFailure, Scope.Scope> {
|
|
689
791
|
const annotate = (report: MaintenancePassReport) =>
|
|
690
792
|
Effect.annotateCurrentSpan({
|
|
@@ -697,7 +799,7 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
697
799
|
|
|
698
800
|
const started = yield* mutations.withSnapshot((activeAtStart) =>
|
|
699
801
|
Effect.gen(function* () {
|
|
700
|
-
const generation = yield* beginPass();
|
|
802
|
+
const generation = yield* beginPass(observed);
|
|
701
803
|
|
|
702
804
|
if (generation._tag === "Actionable" && activeAtStart === 0) {
|
|
703
805
|
// The gate excludes a producer starting between the snapshot and certification.
|
|
@@ -804,7 +906,9 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
804
906
|
|
|
805
907
|
const nativeDeadline =
|
|
806
908
|
active > 0 || state.dirty > state.processed
|
|
807
|
-
?
|
|
909
|
+
? state.retry?.generation === state.dirty && active === 0
|
|
910
|
+
? Math.max(now + minimumAlarmDelay, state.retry.notBefore)
|
|
911
|
+
: now + config.wakeScanInterval
|
|
808
912
|
: Infinity;
|
|
809
913
|
|
|
810
914
|
const next = Option.isSome(latest)
|
|
@@ -837,6 +941,7 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
837
941
|
);
|
|
838
942
|
}
|
|
839
943
|
// Step 2 — reconciliation strictly precedes new work in this pass (exit gate).
|
|
944
|
+
observed.nativeOnly = true;
|
|
840
945
|
const recovered: ReadonlyArray<RecoveryReport> = yield* runtime.runRecovery;
|
|
841
946
|
const reports = new Map(recovered.map((report) => [report.submissionId, report]));
|
|
842
947
|
const current = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
@@ -851,44 +956,121 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
851
956
|
.map((head) => head.threadId)
|
|
852
957
|
.sort();
|
|
853
958
|
|
|
854
|
-
|
|
959
|
+
const selectionTime = yield* Clock.currentTimeMillis;
|
|
855
960
|
|
|
856
|
-
|
|
857
|
-
yield* failpoint.hit("maintenance:select:before");
|
|
858
|
-
selected = yield* runTransaction("select maintenance lane", () =>
|
|
859
|
-
ctx.storage.transaction(async (transaction) => {
|
|
860
|
-
const { state } = await readMaintenanceState(transaction);
|
|
961
|
+
yield* failpoint.hit("maintenance:select:before");
|
|
861
962
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
state.lastServedThreadId === undefined || threadId > state.lastServedThreadId,
|
|
866
|
-
) ?? eligible[0];
|
|
963
|
+
const selection = yield* runTransaction("select maintenance lane", () =>
|
|
964
|
+
ctx.storage.transaction(async (transaction) => {
|
|
965
|
+
const { state } = await readMaintenanceState(transaction);
|
|
867
966
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
await transaction.put(
|
|
872
|
-
MAINTENANCE_STATE_KEY,
|
|
873
|
-
encodeMaintenanceState(
|
|
874
|
-
ThreadMaintenanceState.make({ ...state, lastServedThreadId: next }),
|
|
875
|
-
),
|
|
876
|
-
);
|
|
877
|
-
}
|
|
967
|
+
const retries = (state.bindingRetries ?? []).filter(
|
|
968
|
+
(retry) => heads.get(retry.threadId)?.submissionId === retry.submissionId,
|
|
969
|
+
);
|
|
878
970
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
971
|
+
const runnable = eligible.filter(
|
|
972
|
+
(threadId) =>
|
|
973
|
+
!retries.some(
|
|
974
|
+
(retry) => retry.threadId === threadId && retry.notBefore > selectionTime,
|
|
975
|
+
),
|
|
976
|
+
);
|
|
977
|
+
|
|
978
|
+
const next =
|
|
979
|
+
runnable.find(
|
|
980
|
+
(threadId) =>
|
|
981
|
+
state.lastServedThreadId === undefined || threadId > state.lastServedThreadId,
|
|
982
|
+
) ?? runnable[0];
|
|
983
|
+
|
|
984
|
+
if (next !== undefined) {
|
|
985
|
+
await transaction.put(
|
|
986
|
+
MAINTENANCE_STATE_KEY,
|
|
987
|
+
encodeMaintenanceState(
|
|
988
|
+
ThreadMaintenanceState.make({ ...state, lastServedThreadId: next }),
|
|
989
|
+
),
|
|
990
|
+
);
|
|
991
|
+
}
|
|
884
992
|
|
|
885
|
-
|
|
886
|
-
|
|
993
|
+
return { selected: next, retries };
|
|
994
|
+
}),
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
yield* failpoint.hit("maintenance:select:after");
|
|
998
|
+
|
|
999
|
+
const selected =
|
|
1000
|
+
selection.selected === undefined ? undefined : heads.get(selection.selected);
|
|
1001
|
+
|
|
1002
|
+
let retries = selection.retries;
|
|
1003
|
+
let bindingFailure: DurableBindingFailure | undefined;
|
|
1004
|
+
|
|
1005
|
+
// One FIFO head per event. An unavailable contract is a durable wait for compatible code,
|
|
1006
|
+
// including for children; other local lanes and host deliveries remain independently due.
|
|
887
1007
|
const settlement =
|
|
888
1008
|
selected === undefined
|
|
889
1009
|
? Option.none()
|
|
890
|
-
: yield* runtime.processThreadHead(selected, { yieldAfter })
|
|
1010
|
+
: yield* runtime.processThreadHead(selected.threadId, { yieldAfter }).pipe(
|
|
1011
|
+
Effect.catchTag(["BindingUnavailable", "BindingDigestMismatch"], (failure) => {
|
|
1012
|
+
bindingFailure = failure;
|
|
1013
|
+
|
|
1014
|
+
return Effect.succeed(Option.none());
|
|
1015
|
+
}),
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
if (selected !== undefined) {
|
|
1019
|
+
const previous = retries.find((retry) => retry.submissionId === selected.submissionId);
|
|
1020
|
+
let retry: BindingRetry | undefined;
|
|
1021
|
+
let reportBindingFailure = false;
|
|
1022
|
+
|
|
1023
|
+
if (bindingFailure !== undefined) {
|
|
1024
|
+
const now = yield* Clock.currentTimeMillis;
|
|
1025
|
+
const attempts = Math.min(30, (previous?.attempts ?? 0) + 1);
|
|
1026
|
+
|
|
1027
|
+
reportBindingFailure =
|
|
1028
|
+
previous === undefined || now - previous.reportedAt >= 15 * 60_000;
|
|
1029
|
+
retry = BindingRetry.make({
|
|
1030
|
+
threadId: selected.threadId,
|
|
1031
|
+
submissionId: selected.submissionId,
|
|
1032
|
+
attempts,
|
|
1033
|
+
notBefore: now + Math.min(60_000, 5_000 * 2 ** (attempts - 1)),
|
|
1034
|
+
reportedAt: reportBindingFailure ? now : (previous?.reportedAt ?? now),
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
if (retry !== undefined || previous !== undefined) {
|
|
1038
|
+
// The Attempt released its Claim. Commit its binding wait (or clear) once,
|
|
1039
|
+
// before joining fallible auxiliary work. This local fact neither acknowledges
|
|
1040
|
+
// a generation nor changes the shared alarm; those require event retirement.
|
|
1041
|
+
yield* failpoint.hit("maintenance:binding-retry:before");
|
|
1042
|
+
retries = yield* runTransaction("record submission binding retry", () =>
|
|
1043
|
+
ctx.storage.transaction(async (transaction) => {
|
|
1044
|
+
const { state } = await readMaintenanceState(transaction);
|
|
1045
|
+
|
|
1046
|
+
const bindingRetries = [
|
|
1047
|
+
...(state.bindingRetries ?? []).filter(
|
|
1048
|
+
(entry) => entry.submissionId !== selected.submissionId,
|
|
1049
|
+
),
|
|
1050
|
+
...(retry === undefined ? [] : [retry]),
|
|
1051
|
+
];
|
|
1052
|
+
|
|
1053
|
+
await transaction.put(
|
|
1054
|
+
MAINTENANCE_STATE_KEY,
|
|
1055
|
+
encodeMaintenanceState(ThreadMaintenanceState.make({ ...state, bindingRetries })),
|
|
1056
|
+
);
|
|
1057
|
+
|
|
1058
|
+
return bindingRetries;
|
|
1059
|
+
}),
|
|
1060
|
+
);
|
|
1061
|
+
yield* failpoint.hit("maintenance:binding-retry:after");
|
|
1062
|
+
}
|
|
1063
|
+
if (bindingFailure !== undefined) {
|
|
1064
|
+
yield* reportBindingFailure
|
|
1065
|
+
? Effect.logError(
|
|
1066
|
+
"Thread awaits a compatible binding; original work remains pending",
|
|
1067
|
+
Cause.fail(bindingFailure),
|
|
1068
|
+
)
|
|
1069
|
+
: Effect.logDebug("Thread binding retry remains pending", Cause.fail(bindingFailure));
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
891
1072
|
|
|
1073
|
+
observed.nativeOnly = false;
|
|
892
1074
|
yield* finishAuxiliary;
|
|
893
1075
|
const remaining = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
894
1076
|
const waitingHeads = new Map<ThreadId, boolean>();
|
|
@@ -914,8 +1096,19 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
914
1096
|
Option.isSome(settlement) ||
|
|
915
1097
|
recovered.some((report) => report.disposition === "repaired");
|
|
916
1098
|
|
|
917
|
-
const delay = autonomous ? yield* rearmDelay(progressed) : 0;
|
|
918
1099
|
const now = yield* Clock.currentTimeMillis;
|
|
1100
|
+
const ordinaryDelay = autonomous ? yield* rearmDelay(progressed, started.stalls) : 0;
|
|
1101
|
+
|
|
1102
|
+
const nextEligible = eligible.map(
|
|
1103
|
+
(threadId) =>
|
|
1104
|
+
retries.find((retry) => retry.submissionId === heads.get(threadId)?.submissionId)
|
|
1105
|
+
?.notBefore ?? now,
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
const bindingDelay =
|
|
1109
|
+
nextEligible.length === 0 ? 0 : Math.max(0, Math.min(...nextEligible) - now);
|
|
1110
|
+
|
|
1111
|
+
const delay = Math.max(ordinaryDelay, bindingDelay);
|
|
919
1112
|
|
|
920
1113
|
yield* failpoint.hit("maintenance:finish:before");
|
|
921
1114
|
|
|
@@ -937,9 +1130,22 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
937
1130
|
: started.generation;
|
|
938
1131
|
|
|
939
1132
|
const next = ThreadMaintenanceState.make({
|
|
940
|
-
...state,
|
|
1133
|
+
...Struct.omit(state, ["retry"]),
|
|
941
1134
|
processed,
|
|
942
1135
|
nonterminal: remaining.length,
|
|
1136
|
+
bindingRetries: (state.bindingRetries ?? []).filter((retry) =>
|
|
1137
|
+
remaining.some((row) => row.submissionId === retry.submissionId),
|
|
1138
|
+
),
|
|
1139
|
+
...(autonomous && !progressed
|
|
1140
|
+
? {
|
|
1141
|
+
retry: MaintenanceRetry.make({
|
|
1142
|
+
generation: started.generation,
|
|
1143
|
+
notBefore: now + delay,
|
|
1144
|
+
nativeOnly: true,
|
|
1145
|
+
stalls: Math.min(30, started.stalls + 1),
|
|
1146
|
+
}),
|
|
1147
|
+
}
|
|
1148
|
+
: {}),
|
|
943
1149
|
});
|
|
944
1150
|
|
|
945
1151
|
await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));
|
|
@@ -947,13 +1153,18 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
947
1153
|
// Replace the crash-fallback slot with this pass's bounded backoff. The target
|
|
948
1154
|
// is never earlier than the begin-pass fallback, so workerd does not cancel
|
|
949
1155
|
// this running alarm handler before its report/span can complete.
|
|
1156
|
+
const nativeDeadline =
|
|
1157
|
+
started.activeAtStart > 0 || active > 0 || state.dirty !== started.generation
|
|
1158
|
+
? now + minimumAlarmDelay
|
|
1159
|
+
: now + delay;
|
|
1160
|
+
|
|
950
1161
|
await transaction.setAlarm(
|
|
951
1162
|
Option.isSome(publicationDeadline)
|
|
952
1163
|
? Math.max(
|
|
953
1164
|
now + minimumAlarmDelay,
|
|
954
|
-
Math.min(
|
|
1165
|
+
Math.min(nativeDeadline, publicationDeadline.value),
|
|
955
1166
|
)
|
|
956
|
-
:
|
|
1167
|
+
: nativeDeadline,
|
|
957
1168
|
);
|
|
958
1169
|
|
|
959
1170
|
return "rearmed" as const;
|
|
@@ -992,9 +1203,6 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
992
1203
|
);
|
|
993
1204
|
|
|
994
1205
|
yield* failpoint.hit("maintenance:finish:after");
|
|
995
|
-
if (alarmDisposition === "cleared") {
|
|
996
|
-
yield* Ref.set(stalls, 0);
|
|
997
|
-
}
|
|
998
1206
|
|
|
999
1207
|
return yield* annotate(
|
|
1000
1208
|
MaintenancePassReport.make({
|
|
@@ -1013,9 +1221,19 @@ export class ThreadMaintenance extends Context.Service<
|
|
|
1013
1221
|
const now = yield* Clock.currentTimeMillis;
|
|
1014
1222
|
const yieldAfter = DateTime.makeUnsafe(now + 10 * 60_000);
|
|
1015
1223
|
const dispatchUntil = DateTime.makeUnsafe(now + 14 * 60_000);
|
|
1224
|
+
const observed: MaintenanceObservation = { nativeOnly: false };
|
|
1016
1225
|
|
|
1017
1226
|
return yield* alarm.withWakesDeferred(
|
|
1018
|
-
maintenancePassGate.withPermit(
|
|
1227
|
+
maintenancePassGate.withPermit(
|
|
1228
|
+
Effect.scoped(pass(yieldAfter, dispatchUntil, observed)).pipe(
|
|
1229
|
+
// Close event-owned auxiliary work and release Attempt ownership before
|
|
1230
|
+
// failure rearming, while still holding the pass permit.
|
|
1231
|
+
Effect.onErrorIf(
|
|
1232
|
+
() => true,
|
|
1233
|
+
() => rearmFailure(observed),
|
|
1234
|
+
),
|
|
1235
|
+
),
|
|
1236
|
+
),
|
|
1019
1237
|
);
|
|
1020
1238
|
}).pipe(
|
|
1021
1239
|
// Include permit waiting, recovery and acknowledgement in the event deadline.
|
package/src/CloudflareConfig.ts
CHANGED
|
@@ -90,13 +90,13 @@ export class CloudflareDurableRuntimeConfigValue extends Schema.Class<Cloudflare
|
|
|
90
90
|
producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
|
|
91
91
|
/** Submission ownership lease duration (D5); fences work across Object incarnations. */
|
|
92
92
|
ownershipLeaseDuration: PositiveMillis,
|
|
93
|
-
/** Base delay of
|
|
93
|
+
/** Base delay of exponential backoff when a pass fails or makes no progress. */
|
|
94
94
|
alarmBackoffBase: PositiveMillis,
|
|
95
|
-
/** Ceiling of
|
|
95
|
+
/** Ceiling of exponential backoff after failed or no-progress maintenance passes. */
|
|
96
96
|
alarmBackoffCap: PositiveMillis,
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
98
|
+
* Fallback scan cadence for newly dirty work. Failed/no-progress passes use
|
|
99
|
+
* alarmBackoffCap independently; constructor repair never shortens their retry deadline.
|
|
100
100
|
*/
|
|
101
101
|
wakeScanInterval: PositiveMillis,
|
|
102
102
|
/** `awaitSettlement` ledger re-check cadence when no wake arrives. */
|