Task IRepository.Prepare(Guid commitId) { Logger.Write(LogLevel.Debug, () => $"Repository {typeof(T).FullName} starting prepare {commitId}"); return (Tracked.Values .ToArray() .SelectAsync(async(x) => { try { await x.Stream.VerifyVersion(commitId).ConfigureAwait(false); } catch (VersionException) { await _store.Evict <T>(x.Stream.Bucket, x.Stream.StreamId).ConfigureAwait(false); throw; } })); }
async Task <Guid> IRepository.Commit(Guid commitId, Guid startingEventId, IDictionary <string, string> commitHeaders) { Logger.Write(LogLevel.Debug, () => $"Repository {typeof(T).FullName} starting commit {commitId}"); var written = 0; using (CommitTime.NewContext()) { foreach (var tracked in Tracked.Values) { if (tracked.Stream.TotalUncommitted == 0) { return(startingEventId); } var headers = new Dictionary <string, string>(commitHeaders); var stream = tracked.Stream; Interlocked.Add(ref written, stream.TotalUncommitted); if (stream.StreamVersion != stream.CommitVersion && tracked is ISnapshotting && (tracked as ISnapshotting).ShouldTakeSnapshot()) { Logger.Write(LogLevel.Debug, () => $"Taking snapshot of [{tracked.GetType().FullName}] id [{tracked.StreamId}] version {tracked.Version}"); var memento = (tracked as ISnapshotting).TakeSnapshot(); stream.AddSnapshot(memento, headers); } var evict = true; try { startingEventId = await stream.Commit(commitId, startingEventId, headers).ConfigureAwait(false); await _store.Cache <T>(stream).ConfigureAwait(false); evict = false; } catch (VersionException e) { // If we expected no stream, no reason to try to resolve the conflict if (stream.CommitVersion == -1) { throw new ConflictResolutionFailedException( $"New stream [{tracked.StreamId}] entity {tracked.GetType().FullName} already exists in store"); } Conflicts.Mark(); try { Logger.Write(LogLevel.Debug, () => $"Stream [{tracked.StreamId}] entity {tracked.GetType().FullName} version {stream.StreamVersion} has version conflicts with store - Message: {e.Message}"); // make new clean entity Logger.Write(LogLevel.Debug, () => $"Attempting to resolve conflict with strategy {_conflictResolution.Conflict}"); using (ConflictResolutionTime.NewContext()) { var uncommitted = stream.Uncommitted.ToList(); stream.Flush(false); var clean = await GetUntracked(stream).ConfigureAwait(false); try { var strategy = _conflictResolution.Conflict.Build(_builder, _conflictResolution.Resolver); startingEventId = await strategy.Resolve(clean, uncommitted, commitId, startingEventId, commitHeaders).ConfigureAwait(false); } catch (ConflictingCommandException) { throw new ConflictResolutionFailedException("Failed to resolve stream conflict"); } await _store.Cache <T>(clean.Stream).ConfigureAwait(false); evict = false; } Logger.WriteFormat(LogLevel.Debug, "Stream [{0}] entity {1} version {2} had version conflicts with store - successfully resolved", tracked.StreamId, tracked.GetType().FullName, stream.StreamVersion); ConflictsResolved.Mark(); } catch (AbandonConflictException abandon) { ConflictsUnresolved.Mark(); Logger.WriteFormat(LogLevel.Error, "Stream [{0}] entity {1} has version conflicts with store - abandoning resolution", tracked.StreamId, tracked.GetType().FullName); throw new ConflictResolutionFailedException( $"Aborted conflict resolution for stream [{tracked.StreamId}] entity {tracked.GetType().FullName}", abandon); } catch (Exception ex) { ConflictsUnresolved.Mark(); Logger.WriteFormat(LogLevel.Error, "Stream [{0}] entity {1} has version conflicts with store - FAILED to resolve due to: {3}: {2}", tracked.StreamId, tracked.GetType().FullName, ex.Message, ex.GetType().Name); throw new ConflictResolutionFailedException( $"Failed to resolve conflict for stream [{tracked.StreamId}] entity {tracked.GetType().FullName} due to exception", ex); } } catch (PersistenceException e) { Logger.WriteFormat(LogLevel.Warn, "Failed to commit events to store for stream: [{0}] bucket [{1}] Exception: {3}: {2}", stream.StreamId, stream.Bucket, e.Message, e.GetType().Name); throw; } catch (DuplicateCommitException) { Logger.WriteFormat(LogLevel.Warn, "Detected a double commit for stream: [{0}] bucket [{1}] - discarding changes for this stream", stream.StreamId, stream.Bucket); // I was throwing this, but if this happens it means the events for this message have already been committed. Possibly as a partial message failure earlier. // Im changing to just discard the changes, perhaps can take a deeper look later if this ever bites me on the ass //throw; } finally { if (evict) { await _store.Evict <T>(stream.Bucket, stream.StreamId).ConfigureAwait(false); await _snapstore.Evict <T>(stream.Bucket, stream.StreamId).ConfigureAwait(false); WriteErrors.Mark(); } } } } WrittenEvents.Update(written); Logger.Write(LogLevel.Debug, () => $"Repository {typeof(T).FullName} finished commit {commitId}"); return(startingEventId); }