You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Composite projection does not catch up under the Wolverine-hosted daemon with UseTenantPartitionedEvents (Marten's own daemon does) #3083
A composite projection (Projections.CompositeProjectionFor) does not catch up under the Wolverine-hosted projection daemon when UseTenantPartitionedEvents = true, while:
a plain SingleStreamProjection in the same store does catch up, and
the same composite catches up fine under Marten's own daemon (store.BuildProjectionDaemonAsync() + StartAllAsync()).
So the composite shard is left stuck at its initial mark under Wolverine-managed/hosted distribution, while everything else advances.
Isolation (single variable: the daemon host)
Identical Marten config in both — Conjoined + QuickWithServerTimestamps + UseTenantPartitionedEvents + AllDocumentsAreMultiTenanted, two managed tenants (AddMartenManagedTenantsAsync), one plain projection + one 2-stage CompositeProjectionFor, ~10 streams/tenant appended after the daemon starts (continuous). Marten 9.7.3 / JasperFx 2.9.9 / WolverineFx 6.8.0, PostgreSQL.
Observed DB state after the Wolverine run (2 tenants × 10 streams × 3 events = 60 events; per-tenant sequences so seq_id 1..30 appears once per tenant → count=60, distinct=30, max=30):
HighWaterMark = 30 <- high-water reached the real max(seq_id)
CsPlain:All = 30 <- plain projection caught up
CsComposite:All = 1 <- composite stuck
CsTrip:All = 1
CsTripNotice:All = 1
Daemon log for the composite: Starting single-pass composite rebuild for CsComposite:All over events 0 to 1 then Finished ... proceeding to normal, continuous operation, followed by repeated High Water agent is stale at 60 ... not able to determine a safe harbor sequence. The composite never advances in continuous mode even though the high-water and the plain projection reach 30.
This is NOT the FetchHighestEventSequenceNumber / CompositeReplayExecutor ceiling bug (#4705) — that fix is present and FetchHighestEventSequenceNumber correctly returns max(seq_id). It is specific to how the Wolverine-hosted daemon drives a composite shard's continuous catch-up under partitioning; Marten's own daemon drives the identical composite to completion.
Repro
Two tests, isolated to the daemon host:
PASSES (Marten daemon) — marten/src/TenantPartitionedEventsTests/Daemon/composite_continuous_catchup_multi_tenant_partitioned.cs: plain + composite, 2 managed partitioned tenants, seed after StartAllAsync(), all catch up.
FAILS (Wolverine daemon) — wolverine/src/Persistence/MartenTests/Distribution/composite_side_effects_under_managed_distribution.cs: the same shape under IntegrateWithWolverine + UseWolverineManagedEventSubscriptionDistribution; the composite never materializes (the wait times out at 90s).
Happy to attach the full daemon log or a stack/state dump. This is the VIPLive zorgdeclaraties production runtime (Wolverine-managed distribution + tenant-partitioned events + composite projections).
Narrowing — the JasperFx daemon is NOT at fault; it is specifically the Wolverine hosting layer:
I added a second Marten-only repro that starts each shard via daemon.StartAgentAsync(shardName) (the exact method EventSubscriptionAgent.StartAsync calls) instead of StartAllAsync(), same config (Conjoined + QuickWithServerTimestamps + UseTenantPartitionedEvents + AllDocumentsAreMultiTenanted, 2 managed tenants, plain + composite, seed AFTER the daemon starts):
StartAllAsync() → composite catches up ✅
StartAgentAsync("CsComposite") + StartAgentAsync("CsPlain") → composite also catches up ✅
So the Marten daemon drives the composite to completion via BOTH entry points, including the per-shard StartAgentAsync that Wolverine uses. The failure only appears when the same daemon is hosted by Wolverine's managed event-subscription distribution.
Observed under Wolverine: the composite logs Starting single-pass composite rebuild for CsComposite:All over events 0 to 1 → Finished ... proceeding to normal, continuous operation, then never advances (stuck at 1) while the plain projection and the HighWaterMark both reach the real max(seq_id)=30. No auto-restart / stall messages are logged.
So the defect is in the Wolverine event-subscription-agent hosting layer (EventSubscriptionAgentFamily / EventStoreAgents / EventSubscriptionAgent — managed-distribution path), in how a composite shard's continuous catch-up is driven after its initial (empty) optimized rebuild — not in JasperFxAsyncDaemon.StartAgentAsync itself. Reproduced with UseWolverineManagedEventSubscriptionDistribution = true; also reproduced with a plain IntegrateWithWolverine() (no managed flag). Repro tests available.
Resolved — root cause is a stale Marten dependency, not a Wolverine-layer defect
After reproducing this end-to-end I can confirm there is no bug in Wolverine's event-subscription distribution. The composite stall is the symptom of Marten #4712 (high-water Timestamp under UseTenantPartitionedEvents), and this repo's Directory.Packages.props pins Marten 9.6.0, which predates that fix.
What actually happens on Marten 9.6.0
With UseTenantPartitionedEvents, the store-global HighWaterStatisticsDetector read its Timestamp off the (absent) store-global mt_event_progression row, leaving it at default(DateTimeOffset) = 0001-01-01. The HighWaterAgent then computes safeHarborTime = _current.Timestamp + StaleSequenceThreshold = 0001-01-01 00:00:03, can never determine a safe harbor, and stops advancing:
Starting single-pass composite rebuild for CsComposite:All over events 0 to 1
Shard 'CsPlain:All': Executed updates for Event range ... 0 to 30
Finished ... CsComposite:All, proceeding to normal, continuous operation
High Water agent is stale at 30 ... skipping gap to events marked after 01/01/0001 00:00:03
Daemon projection high water detection was not able to determine a safe harbor sequence, will try again soon (forever)
The composite finishes its premature 0 → 1 optimized rebuild, transitions to continuous, and is then never told to advance to the real high-water (30). The plain projection caught up only because it happened to consume the first good mark before the agent wedged. That ordering-sensitivity is also why this looked flaky.
Proof (clean A/B, nothing else changed)
The repro test (composite_side_effects_under_managed_distribution, the no-side-effects mirror of Marten's own passing repro) behaves like this:
Marten
Result
9.6.0 (this repo's pin)
hang ~90s, composite stuck at 1, stale … 01/01/0001
9.7.3
PASS in ~4s
Marten 9.7.3 already contains #4712. The Marten-only repros passed all along precisely because they build from 9.7.3 source.
Conclusion
No Wolverine code change is required — the daemon distribution layer is correct. The fix is to consume Marten ≥ 9.7.3 (which also pulls JasperFx 2.9.9). Closing as resolved-by-dependency. Bumping the pinned Marten version can be tracked separately if desired.
Validation: bumping this repo's pinned Marten 9.6.0 → 9.7.3 (+ JasperFx 2.8.2 → 2.9.9) builds the full wolverine.slnx clean (0 warnings / 0 errors) and turns the repro green.
Activity
erdtsieck commentedon Jun 12, 2026
Narrowing — the JasperFx daemon is NOT at fault; it is specifically the Wolverine hosting layer:
I added a second Marten-only repro that starts each shard via
daemon.StartAgentAsync(shardName)(the exact methodEventSubscriptionAgent.StartAsynccalls) instead ofStartAllAsync(), same config (Conjoined + QuickWithServerTimestamps + UseTenantPartitionedEvents + AllDocumentsAreMultiTenanted, 2 managed tenants, plain + composite, seed AFTER the daemon starts):StartAllAsync()→ composite catches up ✅StartAgentAsync("CsComposite")+StartAgentAsync("CsPlain")→ composite also catches up ✅So the Marten daemon drives the composite to completion via BOTH entry points, including the per-shard
StartAgentAsyncthat Wolverine uses. The failure only appears when the same daemon is hosted by Wolverine's managed event-subscription distribution.Observed under Wolverine: the composite logs
Starting single-pass composite rebuild for CsComposite:All over events 0 to 1→Finished ... proceeding to normal, continuous operation, then never advances (stuck at 1) while the plain projection and the HighWaterMark both reach the real max(seq_id)=30. No auto-restart / stall messages are logged.So the defect is in the Wolverine event-subscription-agent hosting layer (
EventSubscriptionAgentFamily/EventStoreAgents/EventSubscriptionAgent— managed-distribution path), in how a composite shard's continuous catch-up is driven after its initial (empty) optimized rebuild — not inJasperFxAsyncDaemon.StartAgentAsyncitself. Reproduced withUseWolverineManagedEventSubscriptionDistribution = true; also reproduced with a plainIntegrateWithWolverine()(no managed flag). Repro tests available.erdtsieck commentedon Jun 12, 2026
Resolved — root cause is a stale Marten dependency, not a Wolverine-layer defect
After reproducing this end-to-end I can confirm there is no bug in Wolverine's event-subscription distribution. The composite stall is the symptom of Marten #4712 (high-water
TimestampunderUseTenantPartitionedEvents), and this repo'sDirectory.Packages.propspins Marten 9.6.0, which predates that fix.What actually happens on Marten 9.6.0
With
UseTenantPartitionedEvents, the store-globalHighWaterStatisticsDetectorread itsTimestampoff the (absent) store-globalmt_event_progressionrow, leaving it atdefault(DateTimeOffset)=0001-01-01. TheHighWaterAgentthen computessafeHarborTime = _current.Timestamp + StaleSequenceThreshold=0001-01-01 00:00:03, can never determine a safe harbor, and stops advancing:The composite finishes its premature
0 → 1optimized rebuild, transitions to continuous, and is then never told to advance to the real high-water (30). The plain projection caught up only because it happened to consume the first good mark before the agent wedged. That ordering-sensitivity is also why this looked flaky.Proof (clean A/B, nothing else changed)
The repro test (
composite_side_effects_under_managed_distribution, the no-side-effects mirror of Marten's own passing repro) behaves like this:stale … 01/01/0001Marten 9.7.3 already contains #4712. The Marten-only repros passed all along precisely because they build from 9.7.3 source.
Conclusion
No Wolverine code change is required — the daemon distribution layer is correct. The fix is to consume Marten ≥ 9.7.3 (which also pulls JasperFx 2.9.9). Closing as resolved-by-dependency. Bumping the pinned Marten version can be tracked separately if desired.