BrighterCommand / BrighterCommand/Brighter
MSSQL tests: EnsureDatabaseExists marks the database created even when CREATE DATABASE threw, and its guard cannot span the two TFM processes
- Dominant language
- C#
- Stars
- 2.5k
- Forks
- 296
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 21
Description
`tests/Paramore.Brighter.MSSQL.Tests/Configuration.cs`, `EnsureDatabaseExists`:
```csharp
using var command = connection.CreateCommand();
command.CommandText = $"IF DB_ID('{databaseName}') IS NULL BEGIN CREATE DATABASE {databaseName}; END;";
command.ExecuteNonQuery(); // :45
}
finally
{
s_databaseCreated = true; // set even when ExecuteNonQuery threw
s_semaphoreSlim.Release();
}
```
Two problems, and together they produce a flake that has already cost real time.
**1. The flag is set in `finally`.** If `ExecuteNonQuery` throws, `s_databaseCreated` is still set to `true`, so every subsequent caller skips creation and proceeds against a database that may not exist. The first test to fail reports a connection or missing-object error; the ones after it report something else entirely.
**2. The guard is a process-local `static`, and CI runs two processes.** `sqlserver-ci` runs the suite for `net9.0` and `net10.0` against **one** SQL Server container. A `SemaphoreSlim` and a `static bool` coordinate threads inside one process; they cannot coordinate two. Both processes reach `CREATE DATABASE` for the same database at the same time.
### Observed
On #4331, run [34611891711](https://github.com/BrighterCommand/Brighter/actions/runs/34611891711), 1 of 247:
```text
Paramore.Brighter.MSSQL.Tests.Outbox.Binary.Sync
.WhenStoringAMessageWithARelativeDataSchemaItShouldBeReadBack
Microsoft.Data.SqlClient.SqlException : A severe error occurred on the current command.
at Paramore.Brighter.MSSQL.Tests.Configuration.EnsureDatabase (Configuration.cs:45)
```
It failed in **database setup at `00:00:01.51`**, not in an assertion. The container log for the same job shows the race directly — two sessions allocating a database id at the same instant:
```text
2026-09-11 14:53:55.97 spid51 [DBMgr::FindFreeDatabaseID] Next available DbId EX locked: 5
2026-09-11 14:53:55.97 spid52 [DBMgr::FindFreeDatabaseID] Next available DbId EX locked: 6
```
A re-run of the same commit passed 24 of 24, which is what makes it a flake rather than a defect in the code under test — but the flake is in this helper and will recur.
### Suggested fix
- Move `s_databaseCreated = true` out of `finally` and into the `try`, after `ExecuteNonQuery` returns, so a failure is retried rather than remembered as success.
- Make the creation tolerant of the cross-process race rather than trying to prevent it: `CREATE DATABASE` racing itself surfaces as a specific error, and catching "already exists" and re-probing is the same shape the box provisioners already use. An application-level lock (`sp_getapplock` on `master`) would also work and is what `MsSqlAdvisoryLock` already does elsewhere in this repository.
Diagnosed while working on #4343, which adds tests that call this same path.
Contributor guide
Research direction
Start in tests/Paramore.Brighter.MSSQL.Tests/Configuration.cs at EnsureDatabaseExists, then inspect the MSSQL CI setup for the net9.0 and net10.0 processes and the existing MsSqlAdvisoryLock usage. Run the MSSQL tests, including both target frameworks, and confirm failed creation is retried or re-probed and concurrent setup leaves the database available without flakes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, sql
- Domain
- ci-cd, databases, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100