test(inboundapi): root the shared in-memory SQLite keep-alive so the database cannot vanish mid-test

Script_UsingDatabase_QueriesViaGateway failed in about 2 of 27 full InboundAPI runs. Looping the
suite 30 times captured the failure 4 times, always as "Internal script error". The exception
underneath was SqliteException 'no such table: Machine'.

Cause: the seeding helper opened its keep-alive SqliteConnection in a local. Once the helper
returned, nothing referenced that connection. After a GC (likely during the Roslyn compile, under
full-suite load), Microsoft.Data.Sqlite's pool reclaimed the leaked connection and closed the last
connection to the shared in-memory database, which destroyed it.

Fix: hold the keep-alive in a static field. InboundDatabaseHelperTests had the same latent bug and
gets the same fix. The test now forces GC plus SqliteConnection.ClearAllPools() before the query.
That makes the old pattern fail deterministically and the fix pass deterministically.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-10 06:12:34 -05:00
parent edd0ca672f
commit 463aced7a1
2 changed files with 42 additions and 6 deletions
@@ -940,6 +940,13 @@ public class InboundScriptExecutorTests
var services = new ServiceCollection();
services.AddSingleton<IDatabaseGateway>(SeededSqliteGateway());
using var provider = services.BuildServiceProvider();
// Regression guard for the old flake: force the collection the full suite used to hit during
// the script compile, then the pool reclaim that followed it. An unrooted keep-alive dies here
// deterministically ("Internal script error"); ClearAllPools only closes IDLE connections.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
SqliteConnection.ClearAllPools();
var executor = new InboundScriptExecutor(
NullLogger<InboundScriptExecutor>.Instance, provider);
@@ -1052,11 +1059,26 @@ public class InboundScriptExecutorTests
=> throw new NotImplementedException();
}
private static SqliteGateway SeededSqliteGateway()
/// <summary>
/// Keep-alive for the shared in-memory database, rooted in a static field. It used to be a
/// local in the seeding helper: once the helper returned nothing referenced it, so after a GC
/// (likely during the script compile under a full-suite run) Microsoft.Data.Sqlite's pool
/// reclaimed the leaked connection, closed the last connection and destroyed the database. The
/// query then failed with <c>no such table: Machine</c>, which the script reported as "Internal
/// script error" (4 runs in 30, captured 2026-11-10).
/// </summary>
private static readonly SqliteConnection KeepAlive = OpenKeepAlive();
private static SqliteConnection OpenKeepAlive()
{
var keep = new SqliteConnection("DataSource=file:movein-exec?mode=memory&cache=shared");
keep.Open(); // keep-alive: shared in-memory db lives until process exit
using var cmd = keep.CreateCommand();
keep.Open(); // lives until process exit
return keep;
}
private static SqliteGateway SeededSqliteGateway()
{
using var cmd = KeepAlive.CreateCommand();
cmd.CommandText = "CREATE TABLE IF NOT EXISTS Machine(Code TEXT, SAPID TEXT); DELETE FROM Machine; INSERT INTO Machine VALUES('Z28061A','131453');";
cmd.ExecuteNonQuery();
return new SqliteGateway("DataSource=file:movein-exec?mode=memory&cache=shared");