Esempio n. 1
0
 public void Dispose()
 => _subscription.Dispose();
Esempio n. 2
0
        public CommandProcessor(
            IStreamStore streamStore,
            ICommandProcessorPositionStore positionStore,
            CommandHandlerDispatcher dispatcher,
            Scheduler scheduler,
            ILogger <CommandProcessor> logger)
        {
            if (streamStore == null)
            {
                throw new ArgumentNullException(nameof(streamStore));
            }
            if (positionStore == null)
            {
                throw new ArgumentNullException(nameof(positionStore));
            }
            if (dispatcher == null)
            {
                throw new ArgumentNullException(nameof(dispatcher));
            }

            _scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler));
            _logger    = logger ?? throw new ArgumentNullException(nameof(logger));

            _messagePumpCancellation = new CancellationTokenSource();
            _messageChannel          = Channel.CreateUnbounded <object>(new UnboundedChannelOptions
            {
                SingleReader = true,
                SingleWriter = false,
                AllowSynchronousContinuations = true
            });
            _messagePump = Task.Factory.StartNew(async() =>
            {
                IStreamSubscription subscription = null;
                try
                {
                    while (await _messageChannel.Reader.WaitToReadAsync(_messagePumpCancellation.Token).ConfigureAwait(false))
                    {
                        while (_messageChannel.Reader.TryRead(out var message))
                        {
                            switch (message)
                            {
                            case Subscribe _:
                                logger.LogInformation("Subscribing ...");
                                subscription?.Dispose();
                                var version = await positionStore
                                              .ReadVersion(RoadNetworkCommandQueue, _messagePumpCancellation.Token)
                                              .ConfigureAwait(false);
                                logger.LogInformation("Subscribing as of {0}", version ?? -1);
                                subscription = streamStore.SubscribeToStream(
                                    RoadNetworkCommandQueue,
                                    version,
                                    async(_, streamMessage, token) =>
                                {
                                    var command = new ProcessStreamMessage(streamMessage);
                                    await _messageChannel.Writer
                                    .WriteAsync(command, token)
                                    .ConfigureAwait(false);
                                    await command
                                    .Completion
                                    .ConfigureAwait(false);
                                },
                                    async(_, reason, exception) =>
                                {
                                    if (!_messagePumpCancellation.IsCancellationRequested)
                                    {
                                        await _messageChannel.Writer
                                        .WriteAsync(
                                            new SubscriptionDropped(reason, exception),
                                            _messagePumpCancellation.Token)
                                        .ConfigureAwait(false);
                                    }
                                },
                                    name: "RoadRegistry.BackOffice.CommandHost.CommandProcessor");
                                break;

                            case ProcessStreamMessage process:
                                try
                                {
                                    logger.LogDebug(
                                        "Processing {MessageType} at {Position}",
                                        process.Message.Type, process.Message.Position);

                                    var body = JsonConvert.DeserializeObject(
                                        await process.Message
                                        .GetJsonData(_messagePumpCancellation.Token)
                                        .ConfigureAwait(false),
                                        CommandMapping.GetEventType(process.Message.Type),
                                        SerializerSettings);
                                    var command = new Command(body).WithMessageId(process.Message.MessageId);
                                    await dispatcher(command, _messagePumpCancellation.Token).ConfigureAwait(false);
                                    await positionStore
                                    .WriteVersion(RoadNetworkCommandQueue,
                                                  process.Message.StreamVersion,
                                                  _messagePumpCancellation.Token)
                                    .ConfigureAwait(false);
                                    process.Complete();
                                }
                                catch (Exception exception)
                                {
                                    _logger.LogError(exception, exception.Message);

                                    // how are we going to recover from this? do we even need to recover from this?
                                    // prediction: it's going to be a serialization error, a data quality error, or a bug
                                    //                                    if (process.Message.StreamVersion == 0)
                                    //                                    {
                                    //                                        await positionStore.WriteVersion(RoadNetworkCommandQueue,
                                    //                                            process.Message.StreamVersion,
                                    //                                            _messagePumpCancellation.Token);
                                    //                                    }

                                    process.Fault(exception);
                                }

                                break;

                            case SubscriptionDropped dropped:
                                if (dropped.Reason == SubscriptionDroppedReason.StreamStoreError)
                                {
                                    logger.LogError(dropped.Exception,
                                                    "Subscription was dropped because of a stream store error.");
                                    await scheduler.Schedule(async token =>
                                    {
                                        if (!_messagePumpCancellation.IsCancellationRequested)
                                        {
                                            await _messageChannel.Writer.WriteAsync(new Subscribe(), token).ConfigureAwait(false);
                                        }
                                    }, ResubscribeAfter);
                                }
                                else if (dropped.Reason == SubscriptionDroppedReason.SubscriberError)
                                {
                                    logger.LogError(dropped.Exception,
                                                    "Subscription was dropped because of a subscriber error.");

                                    if (dropped.Exception != null &&
                                        dropped.Exception is SqlException sqlException &&
                                        sqlException.Number == -2 /* timeout */)
                                    {
                                        await scheduler.Schedule(async token =>
                                        {
                                            if (!_messagePumpCancellation.IsCancellationRequested)
                                            {
                                                await _messageChannel.Writer.WriteAsync(new Subscribe(), token).ConfigureAwait(false);
                                            }
                                        }, ResubscribeAfter);
                                    }
                                }

                                break;
                            }
                        }
                    }
                }
                catch (TaskCanceledException)
                {
                    if (logger.IsEnabled(LogLevel.Information))
                    {
                        logger.Log(LogLevel.Information, "CommandProcessor message pump is exiting due to cancellation.");
                    }
                }
                catch (OperationCanceledException)
                {
                    if (logger.IsEnabled(LogLevel.Information))
                    {
                        logger.Log(LogLevel.Information, "CommandProcessor message pump is exiting due to cancellation.");
                    }
                }
                catch (Exception exception)
                {
                    logger.LogError(exception, "CommandProcessor message pump is exiting due to a bug.");
                }
                finally
                {
                    subscription?.Dispose();
                }
            }, _messagePumpCancellation.Token, TaskCreationOptions.LongRunning | TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
        }