marten icon indicating copy to clipboard operation
marten copied to clipboard

Multiple `FetchForWriting` calls can fail to detect concurrency changes

Open elexisvenator opened this issue 4 months ago • 2 comments

FetchForWriting creates an optimistic lock by setting startingVersion property on the stream action. However there is an edge case where if the stream action already exists and already has a startinVersion set, calling FetchForWriting will overwrite the value. This leads to the possibility of the following scenario, where two FetchForWriting calls made against the same stream will not detect a new event written between the two calls.

StoreOptions(opts =>
{
    opts.Projections.LiveStreamAggregation<SomeProjection>();
    opts.Projections.LiveStreamAggregation<SomeOtherProjection>();
});

var streamId = CombGuidIdGeneration.NewGuid();
theSession.Events.StartStream(streamId, new EventA(), new EventA(), new EventA());
await theSession.SaveChangesAsync();
// stream version is now 3

var otherSession = theStore.LightweightSession();
var firstProjection = await otherSession.Events.FetchForWriting<SomeProjection>(streamId);

firstProjection.StartingVersion.ShouldBe(3);
firstProjection.Aggregate.ShouldNotBeNull();
firstProjection.Aggregate.Version.ShouldBe(3);

// in another session, append more events

theSession.Events.Append(streamId, new EventA(), new EventA());
await theSession.SaveChangesAsync();
// stream version is now 5

var secondProjection = await otherSession.Events.FetchForWriting<SomeOtherProjection>(streamId);
secondProjection.Aggregate.ShouldNotBeNull();
secondProjection.Aggregate.Version.ShouldBe(5);

// attempt to append
otherSession.Events.Append(streamId, new EventA());

// should fail with concurrency error
// because current version of the stream (5) is ahead of the first optimistic lock (3)
await otherSession.SaveChangesAsync();

I think the solution to this is likely to only set the StartingVersion if it doesnt exist. This does lead to the problem where marten knows that something is going to fail before the save changes but says nothing, but I don't think this can be avoided as marten does not know if there is even going to be a save changes call at all.

elexisvenator avatar Oct 08 '24 21:10 elexisvenator