Summary
WaitForNonStaleProjectionDataAsync's store-global fallback requires every row in mt_event_progression to reach the initial event sequence — including the HighWaterAllocationFence bookkeeping row, which is not a projection shard and legitimately lags. When that row is present and behind, the wait can never succeed and the test times out.
Surfaced as EventSourcingTests.Aggregation.ancillary_store_enrichment_tests.enrichment_from_ancillary_store_resolves_entity_and_maps_to_projection, but the helper is shared, so any test calling WaitForNonStaleProjectionDataAsync / WaitForNonStaleData can hit it.
Evidence
System.TimeoutException : The projections timed out before reaching the initial sequence of 18
-------------------------------------------
| Shard Name | Sequence |
-------------------------------------------
| HighWaterAllocationFence | 13|
| HighWaterMark | 18|
| Order:All | 18|
-------------------------------------------
at Marten.Events.TestingExtensions.WaitForNonStaleDataAsync(...) in src/Marten/Events/AsyncProjectionTestingExtensions.cs:line 279
at Marten.Events.TestingExtensions.WaitForNonStaleProjectionDataAsync(...) in src/Marten/Events/AsyncProjectionTestingExtensions.cs:line 156
at EventSourcingTests.Aggregation.ancillary_store_enrichment_tests.enrichment_from_ancillary_store_resolves_entity_and_maps_to_projection() in src/EventSourcingTests/Aggregation/ancillary_store_enrichment_tests.cs:line 67
The real work is done — HighWaterMark and the only projection shard Order:All both reached 18. Only the fence row, at 13, holds the wait open.
Root cause
AsyncProjectionTestingExtensions.cs, the non-per-tenant fallback inside isCaughtUp:
if (!perTenant || tenantHighWater.Count == 0)
{
return rows.Count >= projectionsCount && rows.All(x => x.Sequence >= initial.EventSequenceNumber);
}
rows is everything AllProjectionProgress returns, so rows.All(...) sweeps in bookkeeping rows alongside actual projection shards. HighWaterMark happens to satisfy it (it is the mark), but the allocation-fence row tracks a different thing and has no reason to equal the current sequence.
The fence row arrived with the #4953 high-water liveness work (PR #5057, shipped 9.21.0); this helper predates it and was never taught to skip it.
Suggested fix
Exclude non-projection bookkeeping rows from both the rows.All(...) bar and the projectionsCount comparison — i.e. filter rows down to actual projection/subscription shard names before checking, the way the per-tenant branch already keys off shardIdentities. A shared "is this a real shard row" predicate would keep the two branches from drifting again.
Reproduction / scope
- Reproduces in isolation on clean master at
77efabc0f:
dotnet test src/EventSourcingTests/EventSourcingTests.csproj --framework net10.0 --filter "FullyQualifiedName~ancillary_store_enrichment_tests"
- Not caused by any in-flight branch — verified in a separate worktree at that commit with no local changes.
- Intermittent: the same test passed in a full-suite run earlier the same day, so whether the fence row is behind at the moment of the wait is timing-dependent. That makes it a latent flake in CI rather than a hard failure.
Summary
WaitForNonStaleProjectionDataAsync's store-global fallback requires every row inmt_event_progressionto reach the initial event sequence — including theHighWaterAllocationFencebookkeeping row, which is not a projection shard and legitimately lags. When that row is present and behind, the wait can never succeed and the test times out.Surfaced as
EventSourcingTests.Aggregation.ancillary_store_enrichment_tests.enrichment_from_ancillary_store_resolves_entity_and_maps_to_projection, but the helper is shared, so any test callingWaitForNonStaleProjectionDataAsync/WaitForNonStaleDatacan hit it.Evidence
The real work is done —
HighWaterMarkand the only projection shardOrder:Allboth reached 18. Only the fence row, at 13, holds the wait open.Root cause
AsyncProjectionTestingExtensions.cs, the non-per-tenant fallback insideisCaughtUp:rowsis everythingAllProjectionProgressreturns, sorows.All(...)sweeps in bookkeeping rows alongside actual projection shards.HighWaterMarkhappens to satisfy it (it is the mark), but the allocation-fence row tracks a different thing and has no reason to equal the current sequence.The fence row arrived with the #4953 high-water liveness work (PR #5057, shipped 9.21.0); this helper predates it and was never taught to skip it.
Suggested fix
Exclude non-projection bookkeeping rows from both the
rows.All(...)bar and theprojectionsCountcomparison — i.e. filterrowsdown to actual projection/subscription shard names before checking, the way the per-tenant branch already keys offshardIdentities. A shared "is this a real shard row" predicate would keep the two branches from drifting again.Reproduction / scope
77efabc0f:dotnet test src/EventSourcingTests/EventSourcingTests.csproj --framework net10.0 --filter "FullyQualifiedName~ancillary_store_enrichment_tests"