예제 #1
0
#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");
            }
        }
예제 #2
0
            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);
                        }
                    });
                }
            }