Repository navigation
Darling keeps retrying the store at start instead of never collecting after the retry budget (#4508) - #4509
Merged
Conversation
…e retry budget (#4508) After the two-minute retry budget on a retryable store connect/migrate failure runs out, the collection loop now keeps retrying every 60 seconds instead of falling through to the terminal stand-down. A non-retryable failure still stops immediately, exactly as before. The retry decision (fast retry, sustained retry, or stop) is now a pure method, StartupFailureTriage.NextAction, so it can be pinned without a running store.
erikdarlingdata
marked this pull request as ready for review
September 28, 2026 00:50
erikdarlingdata
added a commit
that referenced
this pull request
Sep 28, 2026
…er collecting (#4538) Darling keeps retrying a slow managed PostgreSQL start, and a retryable config load, instead of never collecting. Refs #4508. #4509 gave the store connect and migrate step a sustained retry after the two-minute fast budget. The managed PostgreSQL start and config load still stood down after that budget, so a crash-recovery replay or a slow disk that took longer left a running service that never collected. - The managed PostgreSQL start gets the same third catch arm: a failure still retryable after the fast budget logs a critical line once, then a warning per retry, publishes Retrying rather than Stopped, and tries again on SustainedRetryDelay (60 seconds). - Config load gets the same arm, only for failures it already classifies as retryable. Non-retryable failures still stop. - The status says it is a sustained retry. GET /api/ping drops attempts and adds "sustained": true and "retryEverySeconds" (from SustainedRetryDelay). The step detail ends with "retrying every 60s, attempt N (past the 120s fast budget)", built by one helper so /api/ping, MCP and the Viewer read the same text. A fast retry is unchanged. - Tests: StartupFailureTriageTests source pins for all three sites; CollectorRuntimeStateTests for both ping shapes and the detail phrase; mutations removing the bootstrap arm or the ping change fail them.
erikdarlingdata
added a commit
that referenced
this pull request
Sep 28, 2026
…00Z UTC (#4621) Moves the CHANGELOG entries carried in merged pull-request descriptions into [Unreleased]. The cut is PRs merged after 2026-09-26T17:37:33Z and at or before 2026-09-28T17:40:00Z; the next splice starts after it. - 76 PRs are spliced: Fixed 36, Changed 28 and Added 12, each counted once under its first section. That is 79 bullets: #4481's Fixed entry holds three, and #4548 adds a second group under Added. - 21 PRs have no user-visible entry (None, test-only, CI-only, or deferred to a parent). - The [#4509] link definition, which #4538 also carries, is defined once. - The IMPORTANT upgrade notes from #4489, #4495, #4501, #4506 and #4541 are held for the release cut and are not in this change. - Only CHANGELOG.md changes.
This was referenced Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4508
Why
DarlingWorker.RunCollectionLoopAsync's store connect +PgMigrations.MigrateAsyncretry loop classifies each failure withStartupFailureTriage.IsRetryable. A failure that classifier accepts is retried every 5 seconds for up to 25 attempts or 120 seconds, whichever comes first. Once that budget runs out, the old code fell through to the loop's bare terminal catch — the same one a non-retryable failure hits — logged one CRITICAL line, publishedStopped, and returned. Thatreturncompletes the worker task successfully, so the web and MCP hosts stay up and the process keeps reporting healthy while collection never starts again for the life of the process. A store that is down for longer than two minutes (a longer failover, a slow crash recovery) and a migration rung that can never apply ended up producing the identical outcome, even though the whole point of the classifier is to tell those two cases apart.What changes
StartupFailureTriagegets a new pure decision method,NextAction(attempt, elapsed, exception), returning one of three outcomes (RetryFast,RetrySustained,Stop) plus the delay to use. This is the retry decision, pulled out of the loop into a seam that can be exercised without a running store, a config file, or a migration.StartupFailureTriage.SustainedRetryDelay(60 seconds), for the slower, unbounded retry once the fast budget is spent.DarlingWorker.csgets a third catch arm, between the existing fast-retry arm and the terminal arm: when the failure is still oneIsRetryableaccepts but the fast budget/attempt cap has been spent, it logs a CRITICAL line once (the first time the fast budget runs out), then a WARNING on every later attempt, keeps publishingCollectorRuntimeState.CollectorPhase.Retrying(neverStopped), and retries forever onSustainedRetryDelay.PublishStopped, andreturn.stoppingToken) still ends the loop promptly — the new arm's filter, like the existing ones, excludesOperationCanceledException.The other two triaged start-up sites (config load, managed-PostgreSQL bootstrap) have the same fast-budget-then-stop shape. This PR changes only the store connect/migrate loop that #4508 names.
Test plan
Build:
dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Release -p:EnableWindowsTargeting=true— 0 warnings, 0 errors. Same forLite.Tests.csproj(build-only; targetsnet10.0-windows, cannot run on macOS — CI decides it; no code in that project touches this change).Ran
Darling.Tests.dllin-process on macOS (Microsoft.WindowsDesktop.Appstripped from the runtimeconfig, per the repo's macOS test recipe). No store rig needed — every new pin is pure or a source-census pin.Darling.Tests.StartupFailureTriageTests: 66 passed, 0 failed (was 58 before this PR's new pins).Darling.Tests.CollectorRuntimeStateTests: 15 passed, 0 failed.Darling.Tests.DocCommentHygieneTests: 77 passed, 0 failed (new doc-commented members).Darling.Tests.RepoFileResolutionEquivalenceTests,RepoFileAdoptionTests,CommentFilterAdoptionTests: all green, unaffected.RED on dev (compile-level: the members are new): copied the new test file into a clean detached worktree at dev's tip (
origin/dev, no code changes) and built it there. It fails to compile —StartupFailureTriage.NextAction,StartupRetryDecision, andStartupFailureTriage.SustainedRetryDelaydon't exist on dev at all (11CS0117/CS0103errors). That is expected: the whole fix is a new seam dev doesn't have.Mutation: with the fix built, changed
NextAction's final line fromreturn new NextStartupAction(StartupRetryDecision.RetrySustained, SustainedRetryDelay);toreturn new NextStartupAction(StartupRetryDecision.Stop, TimeSpan.Zero);, rebuilt, and reranStartupFailureTriageTests:NextAction_BudgetSpentButStillRetryable_IsRetrySustainedfailed (65 passed, 1 failed), everything else stayed green. Reverted the line, rebuilt, reran: 66/66 green again — full clean-tree build after the revert is also 0 warnings / 0 errors, confirming nothing was left mutated.New pins added (all in
Darling.Tests.StartupFailureTriageTestsunless noted):NextAction_BudgetSpentButStillRetryable_IsRetrySustained— retryable failure past either cap →RetrySustainedatSustainedRetryDelay. RED on dev (compile failure, see above); this is the mutation target.NextAction_NonRetryableFailure_IsAlwaysStop— non-retryable failure at attempt 1 and at the caps → alwaysStop, zero delay.NextAction_WithinBudget_IsRetryFast— retryable failure inside the fast budget →RetryFastatRetryDelay.TheStoreSustainedRetryArm_LogsCriticalOnceThenWarningPerRetry— source-census pin: the sustained arm's flag is set exactly once and only in the critical branch, both a critical and a warning call site exist, and the arm callsNextActionthrough the seam.TheStoreSustainedRetryArm_PublishesRetryingNotStopped— the sustained arm publishesRetrying, neverStopped, and never returns.CollectorRuntimeStateTests.EveryRetryPublish_ReportsTheTriagesOwnAttemptCapupdated: now expects 4PublishRetryingcall sites (three startup steps plus the store site's new sustained arm), all still reportingStartupFailureTriage.Attempts.TheThreeSitesHaveDistinctBudgetVariablesupdated: now expects 4IsRetryable(ex)occurrences (was 3) and still exactly 3 stopwatches — the sustained arm reusesstoreRetryBudgetrather than starting a fourth.CHANGELOG
SECTION: Fixed
ENTRY:
REF:
[Darling keeps retrying the store at start instead of never collecting after the retry budget (#4508) #4509]: Darling keeps retrying the store at start instead of never collecting after the retry budget (#4508) #4509