/// <summary> Append one or more events to the stream. </summary> /// <remarks> /// Events are assigned consecutive sequence numbers. The FIRST of these /// numbers is returned. /// /// If no events are provided, return null. /// /// If this object's state no longer represents the remote stream (because other /// events have been written from elsewhere), this method will not write any /// events and will return null. The caller should call <see cref="FetchAsync"/> /// until it returns false to have the object catch up with remote state. /// </remarks> public async Task <uint?> WriteAsync(IReadOnlyList <TEvent> events, CancellationToken cancel = default(CancellationToken)) { if (events.Count == 0) { return(null); } if (Position < _minimumWritePosition) { return(null); } if (events.Any(e => e == null)) { throw new ArgumentException(@"No null events allowed", nameof(events)); } var sw = Stopwatch.StartNew(); var rawEvents = events.Select((e, i) => new RawEvent(_lastSequence + (uint)(i + 1), _serializer.Serialize(e))) .ToArray(); try { var result = await Storage.WriteAsync(Position, rawEvents, cancel); _minimumWritePosition = result.NextPosition; if (result.Success) { foreach (var e in rawEvents) { _cache.Enqueue(e); } Position = result.NextPosition; var first = _lastSequence + 1; _lastSequence += (uint)rawEvents.Length; _log?.Debug( $"Wrote {rawEvents.Length} events up to seq {_lastSequence} in {sw.Elapsed.TotalSeconds:F3}s."); return(first); } _log?.Debug($"Collision when writing {rawEvents.Length} events after {sw.Elapsed.TotalSeconds:F3}s."); return(null); } catch (Exception e) { _log?.Error($"When writing {rawEvents.Length} events after seq {_lastSequence}.", e); throw; } }
public async Task <DriverWriteResult> WriteAsync(long position, IEnumerable <RawEvent> events, CancellationToken cancel = new CancellationToken()) { var sw = Stopwatch.StartNew(); try { return(await Inner.WriteAsync(position, events, cancel)); } finally { Trace.WriteLine("WriteAsync " + sw.ElapsedMilliseconds); } }
/// <summary> Appends events to the stream. </summary> /// <remarks> /// The sequence number may not be before the current one. /// You can provide an arbitrarily large number of events, which will then be /// split into multiple writes. /// </remarks> public async Task WriteAsync( IEnumerable <KeyValuePair <uint, TEvent> > events, CancellationToken cancel = default(CancellationToken)) { var sequence = _sequence ?? await LastWrittenAsync(cancel).ConfigureAwait(false); var position = (long)(_position ?? (_position = await _driver.GetPositionAsync(cancel).ConfigureAwait(false))); // If an exception is thrown before we return properly, it might leave the // cached values (_sequence and _position) in an invalid state, so we null // them to cause a re-load on the next call. If returning properly, we'll // set them back to the proper values. _sequence = null; _position = null; var list = new List <RawEvent>(); var otherList = new List <RawEvent>(); // Assigned the current writing task every time 'write' is called. Task writing = Task.FromResult(0); // Writes the events in the list to the driver Func <IReadOnlyList <RawEvent>, Task> write = async written => { if (written.Count == 0) { return; } var result = await _driver.WriteAsync(position, written, cancel) .ConfigureAwait(false); if (!result.Success) { throw new Exception( $"Error writing events {written[0].Sequence} to {written[written.Count - 1].Sequence}"); } position = result.NextPosition; }; foreach (var kv in events) { cancel.ThrowIfCancellationRequested(); var e = kv.Value; var seq = kv.Key; if (seq <= sequence) { throw new ArgumentException($"Out-of-order sequence #{seq}", nameof(events)); } sequence = seq; list.Add(new RawEvent(seq, _serializer.Serialize(e))); // Avoid runaway scheduling (having to write increasingly // large sets of events because write is slower than enumeration // or serialization) // // Also, start a new write as soon as the previous one is done. if (writing.IsCompleted || list.Count > 1000) { await writing.ConfigureAwait(false); // The created task will ALWAYS be awaited, to guarantee // that exceptions bubble up appropriately. writing = write(list); // Do not overwrite the list straight away, as it will // be used during the write process. Instead, keep two // lists and swap them (there is only one write process // and one serialization process running at any given // time, so two lists are enough). var temp = list; list = otherList; otherList = temp; list.Clear(); } } await writing.ConfigureAwait(false); await write(list).ConfigureAwait(false); // Cache the values we reached. _sequence = sequence; _position = position; }
/// <see cref="IStorageDriver.WriteAsync"/> public Task <DriverWriteResult> WriteAsync(long position, IEnumerable <RawEvent> events, CancellationToken cancel = new CancellationToken()) => _source.WriteAsync(position, events, cancel);