#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed private async Task WriteBatchAsync(AtomicWrite aw) { var transaction = Database.CreateTransaction(); var payloads = aw.Payload.AsInstanceOf <IImmutableList <IPersistentRepresentation> >(); foreach (var payload in payloads) { var(bytes, tags) = Extract(payload); // save the payload transaction.SortedSetAddAsync(_journalHelper.GetJournalKey(payload.PersistenceId), bytes, payload.SequenceNr); // notify about a new event being appended for this persistence id transaction.PublishAsync(_journalHelper.GetJournalChannel(payload.PersistenceId), payload.SequenceNr); // save tags foreach (var tag in tags) { transaction.ListRightPushAsync(_journalHelper.GetTagKey(tag), $"{payload.SequenceNr}:{payload.PersistenceId}"); transaction.PublishAsync(_journalHelper.GetTagsChannel(), tag); } } // set highest sequence number key transaction.StringSetAsync(_journalHelper.GetHighestSequenceNrKey(aw.PersistenceId), aw.HighestSequenceNr); // add persistenceId transaction.SetAddAsync(_journalHelper.GetIdentifiersKey(), aw.PersistenceId).ContinueWith(task => { if (task.Result) { // notify about a new persistenceId Database.Publish(_journalHelper.GetIdentifiersChannel(), aw.PersistenceId); } }); if (!await transaction.ExecuteAsync()) { throw new Exception($"{nameof(WriteMessagesAsync)}: failed to write {typeof(IPersistentRepresentation).Name} to redis"); } }
public override void PreStart() { _callback = GetAsyncCallback <IReadOnlyList <IPersistentRepresentation> >(events => { if (events.Count == 0) { if (_currentSequenceNr > _toSequenceNr) { // end has been reached CompleteStage(); } else { switch (_state) { case State.NotifiedWhenQuerying: // maybe we missed some new event when querying, retry _state = State.Idle; Query(); break; case State.Querying: if (_live) { // nothing new, wait for notification _state = State.WaitingForNotification; } else { // not a live stream, nothing else currently in the database, close the stream CompleteStage(); } break; default: Log.Error($"Unexpected source state: {_state}"); FailStage(new IllegalStateException($"Unexpected source state: {_state}")); break; } } } else { var(evts, maxSequenceNr) = events.Aggregate((new List <EventEnvelope>(), _currentSequenceNr), (tuple, pr) => { if (!pr.IsDeleted && pr.SequenceNr >= _currentSequenceNr && pr.SequenceNr <= _toSequenceNr) { tuple.Item1.Add(new EventEnvelope(new Sequence(pr.SequenceNr), pr.PersistenceId, pr.SequenceNr, pr.Payload)); tuple.Item2 = pr.SequenceNr + 1; } else { tuple.Item2 = pr.SequenceNr + 1; } return(tuple); }); _currentSequenceNr = maxSequenceNr; Log.Debug($"Max sequence number is now {maxSequenceNr}"); if (evts.Count > 0) { evts.ForEach(_buffer.Enqueue); Deliver(); } else { // requery immediately _state = State.Idle; Query(); } } }); if (_live) { // subscribe to notification stream only if live stream was required var messageCallback = GetAsyncCallback <(RedisChannel channel, string bs)>(data => { if (data.channel.Equals(_journalHelper.GetJournalChannel(_persistenceId))) { Log.Debug("Message received"); switch (_state) { case State.Idle: // do nothing, no query is running and no client request was performed break; case State.Querying: _state = State.NotifiedWhenQuerying; break; case State.NotifiedWhenQuerying: // do nothing we already know that some new events may exist break; case State.WaitingForNotification: _state = State.Idle; Query(); break; } } else { Log.Debug($"Message from unexpected channel: {data.channel}"); } }); _subscription = _redis.GetSubscriber(); _subscription.Subscribe(_journalHelper.GetJournalChannel(_persistenceId), (channel, value) => { messageCallback.Invoke((channel, value)); }); } else { // start by first querying the current highest sequenceNr // for the given persistent id // stream will stop once this has been delivered _state = State.Initializing; var initCallback = GetAsyncCallback <long>(sn => { if (_toSequenceNr > sn) { // the initially requested max sequence number is higher than the current // one, restrict it to the current one _toSequenceNr = sn; } switch (_state) { case State.QueryWhenInitializing: // during initialization, downstream asked for an element, // let’s query elements _state = State.Idle; Query(); break; case State.Initializing: // no request from downstream, just go idle _state = State.Idle; break; default: Log.Error($"Unexpected source state when initializing: {_state}"); FailStage(new IllegalStateException($"Unexpected source state when initializing: {_state}")); break; } }); _redis.GetDatabase(_database).StringGetAsync(_journalHelper.GetHighestSequenceNrKey(_persistenceId)).ContinueWith(task => { if (!task.IsCanceled || task.IsFaulted) { if (task.Result.IsNull == true) { // not found, close CompleteStage(); } else { initCallback(long.Parse(task.Result)); } } else { Log.Error(task.Exception, "Error while initializing current events by persistent id"); FailStage(task.Exception); } }); } }