Skip to content

Marten 9.22.5

Choose a tag to compare

@jeremydmiller jeremydmiller released this 06 Aug 11:19
· 280 commits to master since this release

Two source-generator and test-harness fixes that both surfaced on projections built through AddProjectionWithServices, plus the JasperFx 2.42.2 adoption they ride on.

Fixes

The source generator no longer breaks a projection that takes dependencies (#5192)

The bundled JasperFx.Events.SourceGenerator registers an EventProjection's discovered published document types (#4166) by writing into your partial class. It used to emit a parameterless constructor to do it, which failed two ways for exactly the projections that need dependencies injected.

It broke the build outright against a primary constructor. C# requires every other constructor to chain through the primary one, so this failed with CS8862 inside the generated <T>.TypeRegistration.g.cs:

public partial class MyProjection(ILogger<MyProjection> logger) : EventProjection
{
    public override ValueTask ApplyAsync(IDocumentOperations operations, IEvent e, CancellationToken cancellation)
    {
        operations.Store(new Thing());
        return new ValueTask();
    }
}

And where it did compile, it silently did nothing. A projection registered through AddProjectionWithServices is built by the container, which calls the dependency-taking constructor — so the generated parameterless one never ran and the published types went unregistered. That also left the projection's teardown targets unregistered, so a rebuild did not wipe its documents.

Registration now rides an override of ProjectionBase.PublishedTypes(), which does not care how the instance was constructed.

Affects 9.22.3 and 9.22.4. Earlier versions discovered published types syntactically, so only an explicit ops.Store<Doc>(x) produced a registration and the far more common ops.Store(x) produced none — which meant the constructor was rarely emitted at all.

One behavior change to be aware of: the generator used to skip registration entirely when your class already had an explicit parameterless constructor, a guard that existed only because you cannot add a second one. An override has no such conflict, so those projections now get their published types registered too. That is the intended #4166 behavior, but on upgrade it can newly provision document storage — and newly register teardown targets — for a projection that was quietly getting neither. If a projection writes into storage that must not be truncated on rebuild, set DeletePublishedTypesOnTeardown = false.

EventProjectionScenario no longer spends its wall clock asleep (#5195, in part)

Almost none of a scenario's time was work. The harness wipes the event store and then starts the daemon, so the high-water agent's first look saw an empty store, read CaughtUp, and settled into SlowPollingTime — one second by default. Every append then raced a sleeping agent, and because the agent returns to CaughtUp after each batch drains, the cost recurred at every batch boundary. Since a boundary is how a scenario says "these appends must land in different daemon batches", the more precisely a test described its batching, the slower it got.

A scenario owns both the appends and the daemon that must notice them, so it now says so directly, through an in-process IDaemonWakeup — a semaphore release, no database round trip and no LISTEN/NOTIFY. Nothing about your store's polling configuration changes.

batch boundaries before after
1 ~1290ms ~300ms
3 ~3357ms ~815ms

A flat ~250ms per boundary remains, from a hard-coded poll delay in WaitForNonStaleDataAsync. That is the other half of #5195 and is still open.

Dependencies

JasperFx / JasperFx.Events 2.42.2. Adopting it also enrolls Marten in the strong-typed identity event-sourcing compliance suite that landed in 2.42.0 (IComplianceStoreRegistrar.RegisterValueType<T>()), taking the shared cross-store suite to 167 passing tests against Marten.