Repository navigation
Darling tests: the nightly's PostgreSQL step no longer fails after every test passes - #5546
Merged
Merged
Conversation
…in stays short
Seven live test classes (28 tests) minted a scratch database and closed only their connection, so every database waited for the process-exit drain. Since the quiesce step was added to each drop, that drain took about 11 seconds for 29 databases. The test runner forces the process out with exit code 1 ("Foreground threads were left running") 10 seconds after the run returns, so the nightly's Darling PostgreSQL step failed with every test passed and the publish job was skipped.
The seven classes now drop their database in the test's finally, and their openers drop it when a skip or a failed setup throws before the caller owns it. A new census test fails for any test method that mints a scratch database and never disposes it. The exit drain also stops starting new drops after 6 seconds and names the databases it leaves, so a future leak cannot push the exit past the runner's wait.
…in flight The drain stopped starting drops after 6 seconds, but a drop already started could still run its quiesce (up to 10 s) and its DROP DATABASE (up to 30 s), and one DROP DATABASE waited 26 seconds for its immediate checkpoint on a local rig, so the exit could still pass xUnit's 11-second window. The drain now runs clusters side by side and up to 16 drops at once under one deadline, returns when it passes whatever is in flight, cancels connects and quiesces at the deadline (never a sent drop), and counts what it did not finish as Unfinished. The one-connect probe of a dead cluster and the quiesce before each drop are kept. Tests: four hanging clusters drain in 3.0 s (12.0 s on the old drain, past the window); a 1-second budget returns in 1.04 s with all three unfinished; a live leak of six databases returns by the budget and accounts for each. Refs #5480
erikdarlingdata
marked this pull request as ready for review
October 8, 2026 08:41
This was referenced Oct 8, 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.
The nightly build's "Darling PostgreSQL tests" step was failing with exit code 1 after every test had passed, so the nightly was never published (the 2026-10-08 nightly, and two branch runs before it). The cause was that seven live test classes left their scratch databases for a clean-up that runs when the test process exits, and that clean-up had grown slower than the 10 seconds the test runner allows. Those classes now drop their own database when each test ends, and the exit clean-up now drops in parallel and returns within one 6-second deadline, even with a drop still running, so a future leak cannot fail a run.
Refs #5480.
No changelog entry (test infrastructure).
Details
What was observed (CI logs)
Darling.Tests Total: 28763, Errors: 0, Failed: 0, Skipped: 102, Not Run: 1, Time: 2450.661sat 07:18:35.7. Then 29 linesScratch database ... outlived its test (...); dropped at process exit.between 07:18:36.1 and 07:18:46.9,Waiting 10 seconds for foreground threads to exit...at 07:18:36.7,[FATAL ERROR] Foreground threads were left running, forcing process exitat 07:18:46.7, exit code 1, publish skipped.Not Run: 1is not the cause. It is in the green runs too (37581130191, 37423416866).D:\a\..., pwsh), not Ubuntu.Cause (the first two points are observed, the rest follows from them)
TestPlatformTestFramework) starts a timer when the run returns: after 1 s it prints "Waiting 10 seconds...", and 10 s later it prints the FATAL line and callsEnvironment.Exit(1)if the process has not exited. Whatever holds the exit past that point fails the step, whatever the tests did.ScratchPostgres's process-exit drain (DropEverythingRemembered), which drops every scratch database a test did not dispose. Its lines are the ones printed between the summary and the FATAL line.ScratchPostgres.CreateAsyncand disposed only their connection:RawPurgeTriggerLiveTests,RawPurgeTriggerGateErrorTests,RawPurgeConvergeLiveTests,RawRepairEpochTriggerLiveTests,RawTablesLeaveCatalogSweepLiveTests,RawGatePerSlotSourceFloorLiveTests,RetentionHoldAndCadenceRawRedirectLiveTests. That is the 28 to 29 databases at exit.QuiesceTimescaleJobsAsyncto every drop, including each drop in the exit drain: two extra connections,alter_jobon every job, and at least two polls 100 ms apart. That matches the move from about 0.17 s to about 0.39 s per database, which put 29 databases past the 10 s window. I did not reproduce the timing here (no test PostgreSQL), so that last link is inferred from the log timestamps and the diff.What changed
RawGatePerSlotSourceFloorLiveTests,RawPurgeConvergeLiveTests,RawPurgeTriggerGateErrorTests,RawPurgeTriggerLiveTests,RawRepairEpochTriggerLiveTests,RawTablesLeaveCatalogSweepLiveTests,RetentionHoldAndCadenceRawRedirectLiveTests: every test'sfinallynow ends withawait scratch.DisposeAsync(), and each opener (OpenAsync/OpenTimescaleAsync) wraps its setup so a skip (no TimescaleDB) or a failed setup drops the database and connection before it rethrows. Before, a skip after minting also leaked.ScratchPostgres.cs: the exit drain runs clusters side by side, and up to 16 drops at once within a cluster (ExitDrainParallelism), under one deadline (ExitDrainBudgetSeconds = 6). It returns at the deadline whatever is in flight. The deadline cancels connects and quiesces. A drop already sent is never cancelled, because an interruptedDROP DATABASEcan leave an invalid database. Each database it did not finish is named on stderr and counted inExitDrainOutcome.Unfinished, and is removed by the next run's start-of-run sweep or goes with the throwaway cluster. It keeps the dead-cluster probe (the first database on a cluster goes alone, so a dead cluster costs one connect) and the quiesce before each drop.QuiesceTimescaleJobsAsynctakes an optional caller token, linked with its 10 s cap.DROP DATABASE(up to 30 s). On a local rig oneDROP DATABASEwaited 26 to 30 s for its immediate checkpoint (pg.log:checkpoint complete: ... sync=16.786 s, total=26.405 s).ScratchDatabaseDisposalCensusTests.cs(new, taggedStage=Guard): scansDarling.Tests/*.csmethod by method and fails for a method that mints a scratch database (directly or through a helper or handle type that returns one) and never disposes it, or for a helper that can skip after minting without disposing. Four detector tests pin the scan itself on small samples.ScratchPostgresExitDrainTests.cs: tests for the deadline. A spent deadline attempts no connect and counts every database as unfinished (Unfinished == 3,Skipped == 0; Skipped now means only "its cluster did not answer"). Four hanging clusters finish inside the runner's window. A 1 s deadline returns in about 1 s. The mixed dead and live test gets a long deadline, because it pins the mix, not the deadline.Evidence for the deadline
Proof
ScratchDatabaseDisposalCensusTests.NoTestLeavesAScratchDatabaseForTheExitDrainfails and names the 7 openers and 27 test methods in exactly those 7 files, nothing else. With the fix it passes. (The detector was first run over all 281 files that mint a database and flagged other shapes that are correct, such as handle types andawait usinghelpers; the scan now accepts them, and flags nothing else in the tree.)ScratchDatabaseDisposalCensusTests: Total 5, Failed 0 (RED run: Total 5, Failed 1).ScratchPostgresExitDrainTests: Total 6, Failed 0, Skipped 1 (the one that needs a PostgreSQL).*Census*,*Ratchet*,*Hygiene*,*Scrub*,*Guard*,*ScratchPostgres*,*ScratchDatabase*,*RawPurge*,*RawRepair*,*RawTables*,*RawGate*,*RetentionHold*): Total 1038, Failed 0, Skipped 94. The skipped ones are the live tests, which need a test PostgreSQL; none was available on this machine, so the changed live classes did not run here. Build: 0 warnings, 0 errors.ScratchPostgresExitDrainTests(9, live ones included),ScratchDatabaseDisposalCensusTests,ScratchPostgresQuiesceCensusTests,LivePostgresCollectionHygieneTests,LiveCleanupConversionRatchetTests,DocCommentHygiene*andRawGatePerSlotSourceFloorLiveTests: Total 124, Failed 0, 0 build warnings.publish=false,test_map=false; publish is skipped whenpublish=false): every job green. Darling PostgreSQL tests:Total: 28773, Errors: 0, Failed: 0, Skipped: 102, Not Run: 1(the Explicit MCP tool schemas exclude DI services by type, closing a reflection race that leaked them into tools/list (issue-4075) #4110 race test), noFATAL ERRORline. No database was left for the exit drain: the onlyoutlived its testlines are the drain tests' own deliberate cases (20010101names, "the leak test", "the live test"). The step moved on 6.2 s after the test summary, against 10.3 s on the passing Oct 7 nightly and a FATAL exit on Oct 8.Not done