fix(site-event-log): queries on own read connection off the actor thread, bounded keyword window; cap trim counts/vacuums rarely (review 2026-10-21 02/P1, 02/P4)

This commit is contained in:
Joseph Doherty
2026-10-22 08:30:35 -04:00
parent f7c4adbaed
commit 3169429c3f
9 changed files with 194 additions and 35 deletions
@@ -45,6 +45,7 @@ public class SiteEventLogger : ISiteEventLogger, IDisposable
internal const int WriteBatchSize = 256;
private readonly SqliteConnection _connection;
private readonly ILocalDb _localDb;
private readonly ILogger<SiteEventLogger> _logger;
private readonly object _writeLock = new();
private readonly Channel<PendingEvent> _writeQueue;
@@ -80,6 +81,7 @@ public class SiteEventLogger : ISiteEventLogger, IDisposable
ArgumentNullException.ThrowIfNull(localDb);
_logger = logger;
_localDb = localDb;
// Already open — CreateConnection returns a live, pragma-configured,
// UDF-registered connection. Calling Open() on it again would throw.
@@ -141,6 +143,25 @@ public class SiteEventLogger : ISiteEventLogger, IDisposable
}
}
/// <summary>
/// Runs <paramref name="func"/> on a short-lived, dedicated READ connection that is
/// NOT serialised on the write lock. <c>site_events</c> lives in a WAL database, so a
/// reader never blocks the single writer (and vice versa): a slow central event-log
/// query (full-scan keyword search at the storage cap) can no longer stall the writer
/// loop's <c>FlushBatch</c> and overflow the bounded write queue (review 2026-10-21
/// 02/P1). The same pattern <c>OperationTrackingStore.GetStatusAsync</c> uses.
/// </summary>
/// <typeparam name="T">The return type of the function.</typeparam>
/// <param name="func">The function to run against the read connection.</param>
/// <returns>The value returned by <paramref name="func"/>.</returns>
internal T WithReadConnection<T>(Func<SqliteConnection, T> func)
{
ArgumentNullException.ThrowIfNull(func);
ObjectDisposedException.ThrowIf(_disposed, this);
using var readConnection = _localDb.CreateConnection();
return func(readConnection);
}
/// <summary>
/// Runs <paramref name="func"/> against the shared connection while holding the
/// write lock. Throws <see cref="ObjectDisposedException"/> if the logger has