Repository navigation
A store with no readable log directory keeps its hourly collector-cost flush - #3985
Merged
erikdarlingdata merged 2 commits intoSep 23, 2026
Merged
Conversation
…t flush (#3971, #3923) The store-log capture's own catch left the re-mask and collector-cost flush on a connection Npgsql had already closed after the capture's 58P01/42501 - lost every hour on a store shaped like the Linux compose image (no log directory at all). The sizing pass shared the outer catch too, so the next statement a store permanently rejects there (#3918 was the last one) could take the whole tick down again. Reopens the connection before the re-mask and the flush run, gives the sizing pass its own narrow catch mirroring the capture's, and pins both from source and against a real store. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv
…, and a Broken connection is closed before the reopen The brief's third item for #3971: 58P01 (no log directory, the Linux compose store's shape) and 42501 (no log read) are store properties, so the capture's catch warns the first time and logs at Debug after that; get_store_log already reports the gap. Any other failure keeps the hourly Warning. The reopen now closes the connection first, because Npgsql refuses to open one that is Broken rather than Closed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv
erikdarlingdata
enabled auto-merge (squash)
September 23, 2026 03:51
erikdarlingdata
deleted the
feature/3971-store-tick-survives-capture-failure
branch
September 23, 2026 04:00
This was referenced Sep 23, 2026
erikdarlingdata
added a commit
that referenced
this pull request
Sep 23, 2026
…3920, #3927, #3931, #3932, #3940, #3942, #3946, #3947, #3950, #3952, #3955, #3956, #3957, #3964, #3965, #3966, #3968, #3972, #3975, #3979, #3980, #3981, #3983, #3984, #3985) (#3989) The wave's fix PRs deliberately carried no CHANGELOG edits (parallel-agent hot-spot protocol); each agent reported its entry and this commit lands them together, byte-verified against origin/dev. Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Closes #3971. Closes #3923.
Why
DarlingWorker.SweepStoreSelfMetricsAsyncruns three passes on one connection every hour: the sizing pass(
StoreSelfMetrics.SweepAsync), the store-log capture (StoreLogSweep.SweepAsync), then the store-logre-mask and the collector-cost flush together. On a store with no readable server-log directory — the Linux
compose store's shape, since the
timescale/timescaledbimage logs to stderr and carries nologdirectoryat all — the capture fails with
58P01, and Npgsql closes the connection outright rather than leaving itidle-but-usable. The re-mask and the collector-cost flush then run on a dead connection and fail with
"Connection is not open," so
get_collector_costnever gets a row on such a store, however long it runs.Separately, the sizing pass shared the tick's OUTER catch with everything below it. #3918 hit this for one
specific statement (a catch-all census failing
42703on TimescaleDB 2.29+): fixing that SQL left the nextstatement a store permanently rejects (catalog drift on a bring-your-own TimescaleDB, a revoked privilege)
able to take the store-log census and the collector-cost flush down with it all over again.
What changes
Darling/PerformanceMonitor.Darling.Service/DarlingWorker.cs:catch (PostgresException ex) when (!PgBaselineProvider.IsCommandTimeout(ex) && connection.State == ConnectionState.Open). A command timeout and a broken connection still fall through to the outer catchunchanged; a server-rejected statement now leaves the tick's other two series alone.
re-mask and the collector-cost flush run (A store with no readable log directory loses the hourly collector-cost flush: the failed store-log capture leaves the shared connection closed #3971). Reopened rather than swapped for a fresh one: it came
from the pool through
_postgres, soOpenAsyncpulls a new physical connection under the same object theawait usingalready owns and disposes.Tests:
StoreLogSelfMonitoringStoreTests.TheSweepRidesTheHourlySelfMetricsTicknow also pins the sizing pass'snew catch by position (between the sizing call and the store-log capture) and by filter (both conditions
in its
whenclause), and pins the reopen check's position between the store-log catch and thecollector-cost flush — so a mutation that removes any of the three, or moves one past where it has to sit,
reds here.
AlertReadFailureSurfaceTests: the new sizing-pass catch is a swallowed-read census site, exempted withthe same reason the outer catch already carries (a metrics write sweep; no alert is judged on its result).
WorkerExemptSites8 -> 9, the tree-widetotalExemptpin 27 -> 28, both with the comment naming the 28thexemption and its reason.
StoreSelfMetricsTickRecoveryLiveTests(own-store,#1776 own-store): against a real scratchdatabase, forces the exact capture failure, asserts the connection really does close, reopens it the same
way the worker does, then runs a real
CollectorCostAccumulator.FlushAsyncand asserts its row lands.Lite has no store self-metrics tick (
grep -rl "SweepStoreSelfMetricsAsync\|StoreSelfMetrics\|CollectorCostAccumulator\|StoreLogSweep" Lite/returns nothing), so there is no Lite parity item.Test plan
fresh cluster (
logging_collectoroff, the default) has nologdirectory at all, soStoreLogSweep.SweepAsyncfails with58P01: could not open directory "log"andconnection.Stategoes straight to
Closed— noALTER SYSTEMneeded to force it, it is the rig's out-of-the-box state.StoreSelfMetricsTickRecoveryLiveTestsproven to fail on the code before this PR: with the reopendisabled, the very next assertion reds (
Expected: Open, Actual: Closed); with that assertion removedtoo, the run reaches
CollectorCostAccumulator.FlushAsyncand throwsSystem.InvalidOperationException: Connection is not open— the exact class of failure A store with no readable log directory loses the hourly collector-cost flush: the failed store-log capture leaves the shared connection closed #3971 reports.Restored, the test passes.
StoreLogSelfMonitoringStoreTests,AlertReadFailureSurfaceTests,StoreLogRemaskLiveTests,StoreSelfMetricsTickRecoveryLiveTests,LivePostgresCollectionHygieneTests(41 + 12, 0 failures).DocCommentHygiene,StartupCommandTimeoutTests(14, 0 failures),McpPayloadContractCensusTests(67, 0 failures).Darling.Testssuite run twice against a dedicated throwaway PostgreSQL 18.4 + TimescaleDB rigwith
DARLING_TEST_PGset. First run: 12801 total, 2 failed. Both investigated:LivePostgresCollectionHygieneTests.EveryClassUsingTheSharedStore_IsSerializedOrDocumentsWhyNotwas areal, self-inflicted miss — the new live test file was missing the
#1776 own-storeexemption commentthe hygiene census requires for any class reaching the store through
ScratchPostgres; fixed by addingthe marker.
CollectionHealthAggregateTests.OutageHole_PolicyRunAfterMissedRuns_JumpsNoGap_AgainstDevPostgresis untouched by this PR and passed 13/13 re-run alone, so it was a one-off flake in the full-suite run.
Second full run after the hygiene fix: 12801 total, 0 failed, 24 skipped (env-gated), 0 errors.
Installer.Testsand any monitored server (sql2022, sql2025) were not touched, per instructions.Follow-up commit (coordinator)
get_store_logstill reports the gap, and any other failure keeps the hourly Warning. Pinned inStoreLogSelfMonitoringStoreTests.StoreLogSelfMonitoringStoreTests,AlertReadFailureSurfaceTests,StartupCommandTimeoutTests,DocCommentHygiene): 130/130, 0 warnings.🤖 Generated with Claude Code
https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv