Skip to content

WaitForNonStaleProjectionDataAsync counts the HighWaterAllocationFence row as a projection shard, so the wait can never complete #5161

Description

@jeremydmiller

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions