public async Task Resolve <T>(T entity, IEnumerable <IFullEvent> uncommitted, Guid commitId, IDictionary <string, string> commitHeaders) where T : class, IEventSource { var sourced = (IEventSourced)entity; var stream = sourced.Stream; var streamName = _streamGen(typeof(T), StreamTypes.Domain, stream.Bucket, stream.StreamId, stream.Parents); Logger.Write(LogLevel.Info, () => $"Resolving {uncommitted.Count()} uncommitted events to stream [{stream.StreamId}] type [{typeof(T).FullName}] bucket [{stream.Bucket}]"); try { await _store.Freeze <T>(stream).ConfigureAwait(false); var latestEvents = await _eventstore.GetEvents(streamName, stream.CommitVersion + 1).ConfigureAwait(false); Logger.Write(LogLevel.Info, () => $"Stream is {latestEvents.Count()} events behind store"); sourced.Hydrate(latestEvents.Select(x => x.Event as IEvent)); Logger.Write(LogLevel.Debug, () => "Merging conflicted events"); try { foreach (var u in uncommitted) { sourced.Conflict(u.Event as IEvent, metadata: new Dictionary <string, string> { { "ConflictResolution", ConcurrencyConflict.ResolveStrongly.ToString() } }); } } catch (NoRouteException e) { Logger.Write(LogLevel.Info, () => $"Failed to resolve conflict: {e.Message}"); throw new ConflictResolutionFailedException("Failed to resolve conflict", e); } Logger.Write(LogLevel.Debug, () => "Successfully merged conflicted events"); if (stream.StreamVersion != stream.CommitVersion && entity is ISnapshotting && ((ISnapshotting)entity).ShouldTakeSnapshot()) { Logger.Write(LogLevel.Debug, () => $"Taking snapshot of {typeof(T).FullName} id [{entity.Id}] version {stream.StreamVersion}"); var memento = ((ISnapshotting)entity).TakeSnapshot(); stream.AddSnapshot(memento); } await _store.WriteStream <T>(commitId, stream, commitHeaders).ConfigureAwait(false); } finally { await _store.Unfreeze <T>(stream).ConfigureAwait(false); } }
public async Task Commit(Guid commitId, IDictionary <string, string> commitHeaders) { var hasSnapshot = _pendingShot == null ? "no" : "with"; Logger.Write(LogLevel.Info, () => $"Event stream [{StreamId}] in bucket [{Bucket}] for type {typeof(T).FullName} commiting {_uncommitted.Count} events, {_outofband.Count} out of band, {hasSnapshot} snapshot"); // Flush events first, guarantee consistency through expected version THEN write snapshots and OOB if (_uncommitted.Any()) { Logger.Write(LogLevel.Debug, () => $"Event stream [{StreamId}] in bucket [{Bucket}] committing {Uncommitted.Count()} events"); await _store.WriteStream <T>(this, commitHeaders).ConfigureAwait(false); } var tasks = new Task[] { Task.Run(() => { if (!_outofband.Any()) { return(Task.CompletedTask); } if (_oobHandler == null) { Logger.Write(LogLevel.Warn, () => $"OOB events were used on stream [{StreamId}] but no publishers have been defined!"); } else { Logger.Write(LogLevel.Debug, () => $"Event stream [{StreamId}] in bucket [{Bucket}] publishing {_outofband.Count} out of band events to {_oobHandler.GetType().Name}"); return(_oobHandler.Publish <T>(Bucket, StreamId, _outofband, commitHeaders)); } return(Task.CompletedTask); }), Task.Run(() => { if (_pendingShot == null) { return(Task.CompletedTask); } Logger.Write(LogLevel.Debug, () => $"Event stream [{StreamId}] in bucket [{Bucket}] committing snapshot"); return(_snapshots.WriteSnapshots <T>(Bucket, StreamId, _pendingShot, commitHeaders)); }) }; await Task.WhenAll(tasks).ConfigureAwait(false); Flush(true); }
public async Task Resolve <T>(T entity, IEnumerable <IFullEvent> uncommitted, Guid commitId, IDictionary <string, string> commitHeaders) where T : class, IEventSource { var sourced = (IEventSourced)entity; // Store conflicting events in memory // After 100 or so pile up pull the latest stream and attempt to write them again var streamName = _streamGen(typeof(T), StreamTypes.Domain, sourced.Stream.Bucket, sourced.Stream.StreamId, sourced.Stream.Parents); var message = new ConflictingEvents { Bucket = sourced.Stream.Bucket, StreamId = sourced.Stream.StreamId, EntityType = typeof(T).AssemblyQualifiedName, Parents = BuildParentList(entity), Events = uncommitted }; var package = new DelayedMessage { MessageId = Guid.NewGuid().ToString(), Headers = new Dictionary <string, string>(), Message = message, Received = DateTime.UtcNow, ChannelKey = streamName }; foreach (var header in commitHeaders) { package.Headers[$"Conflict.{header.Key}"] = header.Value; } await _delay.AddToQueue(ConcurrencyConflict.ResolveWeakly.ToString(), package, streamName) .ConfigureAwait(false); // Todo: make 30 seconds configurable var age = await _delay.Age(ConcurrencyConflict.ResolveWeakly.ToString(), streamName).ConfigureAwait(false); if (!age.HasValue || age < TimeSpan.FromSeconds(30)) { return; } var stream = sourced.Stream; Logger.Write(LogLevel.Info, () => $"Starting weak conflict resolve for stream [{stream.StreamId}] type [{typeof(T).FullName}] bucket [{stream.Bucket}]"); try { await _store.Freeze <T>(stream).ConfigureAwait(false); var delayed = await _delay.Pull(streamName, max : 200).ConfigureAwait(false); // If someone else pulled while we were waiting if (!delayed.Any()) { return; } Logger.Write(LogLevel.Info, () => $"Resolving {delayed.Count()} uncommitted events to stream [{stream.StreamId}] type [{typeof(T).FullName}] bucket [{stream.Bucket}]"); var latestEvents = await _eventstore.GetEvents(streamName, stream.CommitVersion + 1L) .ConfigureAwait(false); Logger.Write(LogLevel.Info, () => $"Stream [{stream.StreamId}] bucket [{stream.Bucket}] is {latestEvents.Count()} events behind store"); sourced.Hydrate(latestEvents.Select(x => x.Event as IEvent)); Logger.Write(LogLevel.Debug, () => $"Merging {delayed.Count()} conflicted events"); try { foreach (var u in delayed.Select(x => x.Message as ConflictingEvents).SelectMany(x => x.Events)) { sourced.Conflict(u.Event as IEvent, metadata: new Dictionary <string, string> { { "ConflictResolution", ConcurrencyConflict.ResolveWeakly.ToString() } }); } } catch (NoRouteException e) { Logger.Write(LogLevel.Info, () => $"Failed to resolve conflict: {e.Message}"); throw new ConflictResolutionFailedException("Failed to resolve conflict", e); } Logger.Write(LogLevel.Info, () => "Successfully merged conflicted events"); if (stream.StreamVersion != stream.CommitVersion && entity is ISnapshotting && ((ISnapshotting)entity).ShouldTakeSnapshot()) { Logger.Write(LogLevel.Debug, () => $"Taking snapshot of [{typeof(T).FullName}] id [{entity.Id}] version {stream.StreamVersion}"); var memento = ((ISnapshotting)entity).TakeSnapshot(); stream.AddSnapshot(memento); } await _store.WriteStream <T>(commitId, stream, commitHeaders).ConfigureAwait(false); } finally { await _store.Unfreeze <T>(stream).ConfigureAwait(false); } }