fix(historian): QuestDB final-review fix wave (I-1, M-1, M-3, M-5, M-8, M-9)
- I-1: QuestDbConnection caps Npgsql Maximum Pool Size at 24 (DefaultMaxPoolSize) when the connection string does not set it; an explicit value is kept. The effective value is logged once at Information. - M-1: QuestDbTagCatalog.EnsureAsync waits for the tag table's sequenced commits and re-checks before inserting, so it no longer overwrites a provisioner row (unit/description) that is committed but not yet applied. Integration test reproduces the clobber against real QuestDB (falsified). - M-3: the pre-1970 drop Warning is rate-limited per tag (once per 10 min); DroppedPre1970 still counts every sample. - M-5: QuestDbHistorianProvider is IAsyncDisposable/IDisposable and disposes the shared Npgsql pool; Program.cs registers the provider through a factory and every seam resolves it from the container, so DI disposes it at shutdown. - M-8, M-9: docs fixes (HistoryUpdate transaction wording; folded duplicate HistoryUpdate paragraph). Pool-size docs updated for the new default cap. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+33
@@ -92,6 +92,39 @@ public sealed class ProvisioningTests(QuestDbFixture fx)
|
||||
(await TagAsync(c, "k")).ShouldBe(((byte)DriverDataType.Float64, "kg", "mass"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Final review M-1: the catalog's first sighting of a tag can run while the provisioner's row is committed but
|
||||
/// not yet applied (its WAL-visibility window). An unconditional catalog INSERT then lands after it and the
|
||||
/// dedup key replaces the provisioner's row with one carrying no unit/description. The provisioner here does
|
||||
/// not wait (VisibilityTimeout 0) and has its own catalog, so every catalog Ensure runs inside that window.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task A_catalog_Ensure_inside_the_provisioners_visibility_window_keeps_its_metadata()
|
||||
{
|
||||
fx.SkipIfUnavailable();
|
||||
var o = fx.Options(fx.UniquePrefix());
|
||||
await using var c = new QuestDbConnection(o);
|
||||
var b = new QuestDbSchemaBootstrapper(c, o);
|
||||
await b.EnsureAsync(CancellationToken.None);
|
||||
var noWait = new QuestDbWalVisibility(c, TimeSpan.Zero);
|
||||
var provisioner = new QuestDbTagProvisioner(c, b, new QuestDbTagCatalog(c, b, noWait), noWait);
|
||||
var catalog = new QuestDbTagCatalog(c, b, new QuestDbWalVisibility(c, o.VisibilityTimeout));
|
||||
|
||||
const int Tags = 25;
|
||||
for (var i = 0; i < Tags; i++)
|
||||
{
|
||||
var name = $"race{i}";
|
||||
(await provisioner.EnsureTagsAsync([new(name, DriverDataType.Float64, "kg", $"mass {i}")], CancellationToken.None))
|
||||
.Ensured.ShouldBe(1);
|
||||
await catalog.EnsureAsync(name, 1, CancellationToken.None); // the recorder's first value for the tag
|
||||
}
|
||||
|
||||
await new QuestDbWalVisibility(c, o.VisibilityTimeout).WaitAsync(c.TagTable, CancellationToken.None);
|
||||
for (var i = 0; i < Tags; i++)
|
||||
(await TagAsync(c, $"race{i}")).ShouldBe(((byte)DriverDataType.Float64, "kg", $"mass {i}"), $"race{i}");
|
||||
(await CountAsync(c)).ShouldBe(Tags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_connection_failure_is_a_Failed_tally_not_an_exception()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user