public async Task Can_manage_errors_with_custom_retry_policy()
        {
            const int expected = 1000;
            var       actual   = 0;
            var       producer = new BackgroundThreadProducer <BaseEvent> {
                MaxDegreeOfParallelism = 10
            };

            producer.AttachError(x =>
            {
                /* compensating for not using an intermediary like Hub, which handles exceptions */
            });
            producer.AttachUndeliverable(new Action <BaseEvent>(x => Interlocked.Increment(ref actual)));
            producer.Attach(new ThrowingHandler());

            producer.RetryPolicy = new RetryPolicy();
            producer.RetryPolicy.After(1, RetryDecision.Undeliverable);

            for (var i = 0; i < expected; i++)
            {
                await producer.Produce(new BaseEvent { Id = i });
            }
            await producer.Start();

            await producer.Stop();
        }
        public async Task Can_requeue_with_exponential_backoff()
        {
            const int expected = 1;
            const int maxTries = 3;

            var actual = 0;

            var producer = new BackgroundThreadProducer <BaseEvent> {
                MaxDegreeOfParallelism = 10
            };

            producer.AttachError(x =>
            {
                /* compensating for not using an intermediary like Hub, which handles exceptions */
            });
            producer.Attach(new ThrowingHandler());
            producer.AttachUndeliverable(x =>
            {
                _console.WriteLine("abandoned message after " + x.Attempts + " attempts");
                Assert.Equal(maxTries, x.Attempts);
                Interlocked.Increment(ref actual);
            });

            producer.RetryPolicy = new RetryPolicy();
            producer.RetryPolicy.After(maxTries, RetryDecision.Undeliverable);

            for (var i = 0; i < expected; i++)
            {
                await producer.Produce(new BaseEvent { Id = i });
            }
            await producer.Start();

            while (producer.Undeliverable < expected)
            {
                await Task.Delay(10);
            }
            await producer.Stop();

            Assert.Equal(expected, actual);
        }
Esempio n. 3
0
        public ScheduledProducer(ScheduledProducerSettings settings = null)
        {
            _settings = settings ?? new ScheduledProducerSettings();

            _schedulers = new ConcurrentDictionary <int, TaskScheduler>();
            _factories  = new ConcurrentDictionary <TaskScheduler, TaskFactory>();
            _pending    = new ConcurrentDictionary <Handler, HandlerMethods>();
            _cancel     = new CancellationTokenSource();
            _threads    = _settings.Concurrency;

            // polling thread
            Background = new BackgroundThreadProducer <IEnumerable <ScheduledTask> >();
            Background.Attach(WithPendingTasks);
            Background.AttachBacklog(WithOverflowTasks);
            Background.AttachUndeliverable(WithFailedTasks);

            // maintenance thread
            Maintenance = new BackgroundThreadProducer <IEnumerable <ScheduledTask> >();
            Maintenance.Attach(WithHangingTasks);
            Maintenance.AttachBacklog(WithHangingTasks);
            Maintenance.AttachUndeliverable(WithHangingTasks);
        }
        public async Task Can_perform_ack_nack_pattern()
        {
            var random = new Random();

            const int expected = 100;
            var       actual   = 0;     // counting finalizations

            var retryPolicy = new RetryPolicy();

            retryPolicy.Default(RetryDecision.Backlog);             // use external requeuing
            retryPolicy.After(3, RetryDecision.Undeliverable);

            //
            // ACK/NACK:
            // =========
            // 1. The main thread pushes messages, while two consumer producers pull messages.
            // 2. If the message fails (50% probability), it is returned to the push thread.
            //    The number of attempts are retained when the message is relayed. This way
            //    the retry policy is enforced across all consumers, provided each consumer
            //    implements the same policy.

            var producer = new BackgroundThreadProducer <BaseEvent> {
                MaxDegreeOfParallelism = 1
            };
            var consumer1 = new BackgroundThreadProducer <BaseEvent> {
                MaxDegreeOfParallelism = 1
            };
            var consumer2 = new BackgroundThreadProducer <BaseEvent> {
                MaxDegreeOfParallelism = 1
            };

            //
            // Consumer 1:
            consumer1.AttachUndeliverable(x =>
            {
                _console.WriteLine("[consumer2] message " + x.Message.Id + " abandoned after " + x.Attempts + " attempt(s)");
                Interlocked.Increment(ref actual);                 // undelivered
            });
            consumer1.AttachBacklog(async x =>
            {
                _console.WriteLine("[consumer1] message " + x.Message.Id + " requeued");
                await SendToRandomConsumer(x);                 // passes context data to other consumers
            });
            consumer1.Attach(new ActionConsumer <BaseEvent>(x =>
            {
                if (NextBool(random))
                {
                    throw new Exception();
                }
                Interlocked.Increment(ref actual);                 // sent
            }));
            consumer1.RetryPolicy = retryPolicy;
            await consumer1.Start();

            //
            // Consumer 2:
            consumer2.AttachUndeliverable(x =>
            {
                _console.WriteLine("[consumer2] message " + x.Message.Id + " abandoned after " + x.Attempts + " attempt(s)");
                Interlocked.Increment(ref actual);                 // undelivered
            });
            consumer2.AttachBacklog(async x =>
            {
                _console.WriteLine("[consumer2] message " + x.Message.Id + " requeued");
                await SendToRandomConsumer(x);                 // passes context data to other consumers
            });
            consumer2.Attach(new ActionConsumer <BaseEvent>(x =>
            {
                if (NextBool(random))
                {
                    throw new Exception();
                }
                Interlocked.Increment(ref actual);                 // sent
            }));
            consumer2.RetryPolicy = retryPolicy;
            await consumer2.Start();

            async Task SendToRandomConsumer(QueuedMessage <BaseEvent> x)
            {
                if (NextBool(random))
                {
                    _console.WriteLine($"message {x.Message.Id} sent to consumer1 ({x.Attempts} attempt(s))");
                    await consumer1.Produce(x);
                }
                else
                {
                    _console.WriteLine($"message {x.Message.Id} sent to consumer2 ({x.Attempts} attempt(s))");
                    await consumer2.Produce(x);
                }
            }

            //
            // Producer:
            for (var i = 0; i < expected; i++)
            {
                await producer.Produce(new BaseEvent { Id = i });
            }
            producer.Attach(async x => { await SendToRandomConsumer(x); });
            await producer.Start();             // start with a full buffer

            while (actual != expected)          // wait for all finalizations
            {
                await Task.Delay(10);
            }
        }