Skip to content

Describe what the fixture's class-per-database actually buys - #2385

Merged
erikdarlingdata merged 1 commit into
devfrom
fix/2376-fixture-comment
Aug 20, 2026
Merged

Describe what the fixture's class-per-database actually buys#2385
erikdarlingdata merged 1 commit into
devfrom
fix/2376-fixture-comment

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Refs #2376, reduced scope after correcting my own numbers on that issue.

I filed #2376 saying the process-wide lock was costing a 500-second suite. That figure came from the one run that FAILED — the flake. Successful runs are 190–222 s. I generalised from a single outlier, so the "restructure 42 classes into a collection" idea is not justified and I am not proposing it.

What survives is smaller and still worth fixing: the comment asserts a property the lock does not permit.

Each class owns its own database file, keeping xUnit's cross-class parallelism intact.

DuckDbInitializer's lock is private static readonly, so every read and write in the suite queues on one process-wide lock regardless of which database file it targets. The classes serialize anyway — through a lock instead of through a collection, and without a collection's ordering. The file separation is real, but what it buys is the schema-build cost running concurrently (~80 DDL statements per class), which is what the fixture exists to amortise. That is now what the comment says.

A comment stating a false invariant is worse than no comment, because the next reader reasons from it — I did, while diagnosing #2374, and it cost me a wrong hypothesis before I checked the lock's declaration.

Two things deliberately included:

  • That the lock must NOT be narrowed. Production creates several DuckDbInitializer instances over the same App.DatabasePath (MainWindow, DatabaseStateOverridesWindow, DuckDbAlertHistoryStore), and the static lock is what keeps them mutually exclusive. Per-instance is the obvious-looking fix and would trade a slow suite for a real data race. Better to say so where someone would go looking.
  • The practical consequence, since it already cost a nightly: scheduling pressure can starve the 5-second write-lock acquisition in GetDatabaseStateDeviationsAsync, whose maintenance block is best-effort and skips on timeout (Flaky: DatabaseStateExpectedStoreTests.ResetToCurrent_RebaselinesAndClearsOverride blocked a nightly with no Lite change #2374).

Comment only, no behaviour change. The LockRecursionException → no-op-disposable swallow noted on the issue is left untouched — with NoRecursion it fires for any nesting rather than only the leak its comment describes, but I have not tied it to an observed failure, and changing lock semantics over a comment-level concern is a bad trade.

The comment claimed IClassFixture keeps "xUnit's cross-class parallelism
intact". The file separation is real, but DuckDbInitializer's lock is
private static readonly, so every read and write in the suite queues on
one process-wide lock regardless of which database file it targets. The
classes serialize anyway -- through a lock instead of through a
collection, and without a collection's ordering.

What the separation does buy is the schema-build cost, which is the
thing the fixture exists to amortise: ~80 DDL statements per class,
running concurrently.

Says explicitly that the lock must NOT be narrowed to fix this.
Production creates several DuckDbInitializer instances over the same
App.DatabasePath, and the static lock is what keeps them mutually
exclusive -- per-instance would trade a slow suite for a real data race,
which is the obvious wrong fix.

Also records the practical consequence, since it already cost a nightly:
scheduling pressure can starve the 5-second write-lock acquisition in
GetDatabaseStateDeviationsAsync, whose maintenance block is best-effort
and skips on timeout (#2374).

Comment only, no behaviour change.

Refs #2376
@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Reviewed. This is a comment-only change to Lite.Tests/SharedDuckDbFixture.cs (no behavior change, confirmed via diff — only the XML doc summary is touched).

I verified the factual claims the new comment makes against the actual code:

  • DuckDbInitializer.s_dbLock is indeed private static readonly (Lite/Database/DuckDbInitializer.cs:26), so it's one process-wide lock regardless of which database file an instance targets — the comment's central claim checks out.
  • Production does create multiple DuckDbInitializer instances over App.DatabasePath: MainWindow.xaml.cs:97, DatabaseStateOverridesWindow.xaml.cs:59, and DuckDbAlertHistoryStore.cs (falls back to App.DatabasePath when no initializer is injected) — confirms the "must not be narrowed" rationale.
  • LocalDataService.GetDatabaseStateDeviationsAsync does gate its maintenance block on a 5-second write-lock acquisition and skips it (best-effort) on timeout, logging via AppLogger.Warn — matches the comment's description of the Flaky: DatabaseStateExpectedStoreTests.ResetToCurrent_RebaselinesAndClearsOverride blocked a nightly with no Lite change #2374 failure mode.

No correctness, parity, security, or performance concerns:

  • Parity: this is Lite-only test infrastructure (DuckDB fixture); Darling's test suite has no equivalent fixture, so there's nothing to keep in sync.
  • No T-SQL touched, so the collector style conventions don't apply here.
  • XML doc tags (<para>/<c>) are balanced and nest correctly inside <summary>.

Good catch turning a stale/false invariant into an accurate one before it misled another reader the way it apparently did during #2374 triage.

@erikdarlingdata
erikdarlingdata merged commit b5ffeaa into dev Aug 20, 2026
6 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/2376-fixture-comment branch August 20, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant