Beispiel #1
0
    public Bridge(string leftName, string rightName, bool autoCreateQueues, string autoCreateQueuesIdentity, EndpointInstances endpointInstances, ISubscriptionStorage subscriptionStorage, IDistributionPolicy distributionPolicy, string poisonQueue, Action <TransportExtensions <TLeft> > leftCustomization, Action <TransportExtensions <TRight> > rightCustomization, int?maximumConcurrency)
    {
        this.endpointInstances   = endpointInstances;
        this.subscriptionStorage = subscriptionStorage;
        publishRouter            = new PublishRouter(subscriptionStorage, distributionPolicy);
        sendRouter  = new SendRouter(endpointInstances, distributionPolicy);
        replyRouter = new ReplyRouter();

        leftConfig = RawEndpointConfiguration.Create(leftName, (context, _) => Forward(context, rightStartable, rightSubscribeRouter), poisonQueue);
        var leftTransport = leftConfig.UseTransport <TLeft>();

        leftTransport.GetSettings().Set("errorQueue", poisonQueue);
        leftCustomization?.Invoke(leftTransport);
        if (autoCreateQueues)
        {
            leftConfig.AutoCreateQueue(autoCreateQueuesIdentity);
        }

        rightConfig = RawEndpointConfiguration.Create(rightName, (context, _) => Forward(context, leftStartable, leftSubscribeRouter), poisonQueue);
        var rightTransport = rightConfig.UseTransport <TRight>();

        rightTransport.GetSettings().Set("errorQueue", poisonQueue);
        rightCustomization?.Invoke(rightTransport);
        if (autoCreateQueues)
        {
            rightConfig.AutoCreateQueue(autoCreateQueuesIdentity);
        }

        if (maximumConcurrency.HasValue)
        {
            leftConfig.LimitMessageProcessingConcurrencyTo(1);
            rightConfig.LimitMessageProcessingConcurrencyTo(1);
        }
    }
        static async Task <IRawEndpointInstance> StartMonitor()
        {
            var queueMonitorContext = new DefaultMetricsContext("QueueLengthMonitor");

            new MetricsConfig(queueMonitorContext)
            .WithHttpEndpoint("http://localhost:7777/QueueLengthMonitor/")
            .WithReporting(r =>
            {
                r.WithReport(
                    new PerformanceCounterReporter(x => new CounterInstanceName("Queue Length", x.MetricName)),
                    TimeSpan.FromSeconds(5), Filter.New.WhereContext(c => c == "QueueLengthMonitor" || c == "QueueState"));
            });

            var monitor = new Monitor(queueMonitorContext, new QueueLength());
            var config  = RawEndpointConfiguration.Create("QueueLengthMonitor", monitor.OnMessage);

            config.LimitMessageProcessingConcurrencyTo(1);
            config.UseTransport <MsmqTransport>();
            //config.UseTransport<RabbitMQTransport>().ConnectionString("host=localhost");
            config.SendFailedMessagesTo("error");

            var endpoint = await RawEndpoint.Start(config);

            return(endpoint);
        }
Beispiel #3
0
        internal static async Task <(IReceivingRawEndpoint, StateStore)> SetupEndpoint(Action <Guid, Message, Message[]> messageProcessed)
        {
            var storageTable = await PrepareStorageTable();

            var stateStore     = new StateStore(storageTable);
            var handlerInvoker = new HandlerInvoker(stateStore);

            var endpointConfiguration = RawEndpointConfiguration.Create(
                endpointName: EndpointName,
                onMessage: async(c, d) =>
            {
                var message = Serializer.Deserialize(c.Body, c.Headers);

                var outputMessages = await handlerInvoker.Process(message);

                var runId = Guid.Parse(c.Headers["Message.RunId"]);

                messageProcessed(runId, message, outputMessages);

                await d.Send(outputMessages, runId);
            },
                poisonMessageQueue: "error");

            endpointConfiguration.UseTransport <LearningTransport>()
            .Transactions(TransportTransactionMode.ReceiveOnly);

            var defaultFactory = LogManager.Use <DefaultFactory>();

            defaultFactory.Level(LogLevel.Debug);

            var endpoint = await RawEndpoint.Start(endpointConfiguration);

            return(endpoint, stateStore);
        }
Beispiel #4
0
    static async Task Main()
    {
        Console.Title = "Case00041163.OccasionalSubscriber";

        var endpointConfiguration = RawEndpointConfiguration.Create(Console.Title, (context, messages) => Task.CompletedTask, "error");

        endpointConfiguration.UseTransport <MsmqTransport>();

        var endpointInstance = await RawEndpoint.Start(endpointConfiguration)
                               .ConfigureAwait(false);

        var subscriptionMessage = ControlMessageFactory.Create(MessageIntentEnum.Subscribe);

        subscriptionMessage.Headers[Headers.SubscriptionMessageType]    = typeof(MyEvent).AssemblyQualifiedName;
        subscriptionMessage.Headers[Headers.ReplyToAddress]             = "Case00041163.OccasionalSubscriber@FAKEPC";
        subscriptionMessage.Headers[Headers.SubscriberTransportAddress] = "Case00041163.OccasionalSubscriber@FAKEPC";
        subscriptionMessage.Headers[Headers.SubscriberEndpoint]         = "Case00041163.OccasionalSubscriber";
        subscriptionMessage.Headers[Headers.TimeSent]           = DateTimeExtensions.ToWireFormattedString(DateTime.UtcNow);
        subscriptionMessage.Headers[Headers.NServiceBusVersion] = "5.6.4";

        var operation = new TransportOperation(subscriptionMessage, new UnicastAddressTag("Case00041163.Publisher"));

        await endpointInstance.Dispatch(new TransportOperations(operation), new TransportTransaction(), new ContextBag()).ConfigureAwait(false);

        await endpointInstance.Stop().ConfigureAwait(false);
    }
    RawEndpointConfiguration PrepareConfig(string inputQueue, string poisonMessageQueueName, Action <TransportExtensions <T> > transportCustomization,
                                           Func <MessageContext, IDispatchMessages, Task> onMessage, PoisonMessageHandling poisonMessageHandling, int?maximumConcurrency,
                                           int immediateRetries, int delayedRetries, int circuitBreakerThreshold, bool autoCreateQueue, string autoCreateQueueIdentity)
    {
        var circuitBreaker = new RepeatedFailuresCircuitBreaker(inputQueue, circuitBreakerThreshold, e =>
        {
            logger.Error($"Persistent error while processing messages in {inputQueue}. Entering throttled mode.", e);
            Task.Run(async() =>
            {
                await transitionSemaphore.WaitAsync().ConfigureAwait(false);
                try
                {
                    var oldEndpoint     = endpoint;
                    var throttledConfig = PrepareThrottledConfig(inputQueue, poisonMessageQueueName, transportCustomization, onMessage, poisonMessageHandling, maximumConcurrency, immediateRetries, circuitBreakerThreshold, delayedRetries);
                    var newEndpoint     = await RawEndpoint.Start(throttledConfig).ConfigureAwait(false);
                    endpoint            = newEndpoint;
                    await oldEndpoint.Stop().ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    logger.Error("Error when entering throttled mode", ex);
                }
                finally
                {
                    transitionSemaphore.Release();
                }
            });
        });
        var regularConfig = RawEndpointConfiguration.Create(inputQueue, async(context, dispatcher) =>
        {
            await onMessage(context, dispatcher).ConfigureAwait(false);
            circuitBreaker.Success();
        }, poisonMessageQueueName);

        regularConfig.CustomErrorHandlingPolicy(new RegularModePolicy(inputQueue, circuitBreaker, poisonMessageHandling, immediateRetries, delayedRetries));

        Task onCriticalError(ICriticalErrorContext context)
        {
            logger.Fatal($"The receiver for queue {inputQueue} has encountered a severe error that is likely related to the connectivity with the broker or the broker itself.");
            return(Task.CompletedTask);
        }

        regularConfig.Settings.Set("onCriticalErrorAction", (Func <ICriticalErrorContext, Task>)onCriticalError);

        var transport = regularConfig.UseTransport <T>();

        transportCustomization(transport);
        if (autoCreateQueue)
        {
            regularConfig.AutoCreateQueue(autoCreateQueueIdentity);
        }
        if (maximumConcurrency.HasValue)
        {
            regularConfig.LimitMessageProcessingConcurrencyTo(maximumConcurrency.Value);
        }
        return(regularConfig);
    }
        public RawEndpointConfiguration CreateRawEndpointConfiguration(string name, Func <MessageContext, IDispatchMessages, Task> onMessage, TransportDefinition transportDefinition)
        {
            var config = RawEndpointConfiguration.Create(name, onMessage, $"{transportSettings.EndpointName}.Errors");

            config.LimitMessageProcessingConcurrencyTo(settings.MaximumConcurrencyLevel);

            transportCustomization.CustomizeRawEndpoint(config, transportSettings);
            return(config);
        }
        /// <summary>
        /// Starts the endpoint.
        /// </summary>
        public async Task Start()
        {
            var outConfig = RawEndpointConfiguration.Create(storageQueueName, OnOutgoingMessage, poisonMessageQueue);

            outConfig.LimitMessageProcessingConcurrencyTo(1);
            transportCustomization(outConfig.UseTransport <T>());
            outConfig.CustomErrorHandlingPolicy(new RetryForeverPolicy());
            outConfig.AutoCreateQueue();

            outEndpoint = await RawEndpoint.Start(outConfig).ConfigureAwait(false);
        }
Beispiel #8
0
        static async Task Main(string[] args)
        {
            var senderConfig = RawEndpointConfiguration.Create("DummyHandler", OnMessage, "error");
            var transport    = senderConfig.UseTransport <AzureServiceBusTransport>();

            transport.ConnectionString(Environment.GetEnvironmentVariable("AzureServiceBus_ConnectionString"));
            senderConfig.LimitMessageProcessingConcurrencyTo(10);

            var sender = await RawEndpoint.Start(senderConfig).ConfigureAwait(false);

            Console.ReadLine();
        }
Beispiel #9
0
        public override async Task Start(CancellationToken token)
        {
            var config = RawEndpointConfiguration.Create("poison", OnMessage, "poison");

            config.AutoCreateQueue();
            config.CustomErrorHandlingPolicy(new IgnoreErrorsPolicy());
            var transport = config.UseTransport <TestTransport>();

            transportConfiguration(transport);

            endpoint = await RawEndpoint.Start(config);
        }
    RawEndpointConfiguration PrepareThrottledConfig(string inputQueue, string poisonMessageQueueName, Action <TransportExtensions <T> > transportCustomization,
                                                    Func <MessageContext, IDispatchMessages, Task> onMessage, PoisonMessageHandling poisonMessageHandling, int?maximumConcurrency,
                                                    int immediateRetries, int delayedRetries, int circuitBreakerThreshold)
    {
        var switchedBack    = false;
        var throttledConfig = RawEndpointConfiguration.Create(inputQueue, async(context, dispatcher) =>
        {
            await onMessage(context, dispatcher);
            if (switchedBack)
            {
                return;
            }
            await transitionSemaphore.WaitAsync().ConfigureAwait(false);
            Task.Run(async() =>
            {
                logger.Info("Exiting throttled mode.");
                try
                {
                    var oldEndpoint   = endpoint;
                    var regularConfig = PrepareConfig(inputQueue, poisonMessageQueueName, transportCustomization, onMessage, poisonMessageHandling, maximumConcurrency, immediateRetries, delayedRetries, circuitBreakerThreshold, false, null);
                    var newEndpoint   = await RawEndpoint.Start(regularConfig).ConfigureAwait(false);
                    endpoint          = newEndpoint;
                    await oldEndpoint.Stop().ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    logger.Error("Error when exiting throttled mode", e);
                }
                finally
                {
                    transitionSemaphore.Release();
                }
            }).Ignore();
            switchedBack = true;
        }, poisonMessageQueueName);

        throttledConfig.CustomErrorHandlingPolicy(new ThrottledModePolicy(inputQueue, immediateRetries));

        Task onCriticalError(ICriticalErrorContext context)
        {
            logger.Fatal($"The receiver for queue {inputQueue} has encountered a severe error that is likely related to the connectivity with the broker or the broker itself.");
            return(Task.CompletedTask);
        }

        throttledConfig.Settings.Set("onCriticalErrorAction", (Func <ICriticalErrorContext, Task>)onCriticalError);

        var transport = throttledConfig.UseTransport <T>();

        transportCustomization(transport);
        throttledConfig.LimitMessageProcessingConcurrencyTo(1);
        return(throttledConfig);
    }
Beispiel #11
0
        RawEndpointConfiguration CreateEndpointConfig(KeyValuePair <string, QueueInfo> queueInfo, Task <bool> barrier, string errorQueue)
        {
            var config    = RawEndpointConfiguration.Create(queueInfo.Value.QueueTable, (context, dispatcher) => OnMessage(context, dispatcher, barrier, queueInfo.Value.Catalogs), errorQueue);
            var transport = config.UseTransport <SqlServerTransport>()
                            .ConnectionString(queueInfo.Value.ConnectionString)
                            .DefaultSchema(queueInfo.Value.Schema);

            transport.GetSettings().Set <EndpointInstances>(new EndpointInstances());

            config.AutoCreateQueue();
            config.DefaultErrorHandlingPolicy(errorQueue, 5);
            return(config);
        }
Beispiel #12
0
 public static async Task InitServerEndpoint(string endpointName)
 {
     if (serverEndpoint == null)
     {
         var senderConfig = RawEndpointConfiguration.Create(
             endpointName: endpointName,
             onMessage: OnMessage,
             poisonMessageQueue: "error");
         senderConfig.UseTransport <MsmqTransport>();
         serverEndpoint = await RawEndpoint.Start(senderConfig)
                          .ConfigureAwait(false);
     }
 }
Beispiel #13
0
 public async static Task InitSubTwoEndpoint(string endpointName, Func <MessageContext, IDispatchMessages, Task> func)
 {
     if (subscriber2Endpoint == null)
     {
         var senderConfig = RawEndpointConfiguration.Create(
             endpointName: endpointName,
             onMessage: func,
             poisonMessageQueue: "error");
         senderConfig.UseTransport <MsmqTransport>();
         senderConfig.AutoCreateQueue();
         subscriber2Endpoint = await RawEndpoint.Start(senderConfig)
                               .ConfigureAwait(false);
     }
 }
Beispiel #14
0
    public Task <ComponentRunner> CreateRunner(RunDescriptor run)
    {
        var typedScenarioContext = (TContext)run.ScenarioContext;

        var sendOnly = onMessage == null;
        var config   = sendOnly
            ? RawEndpointConfiguration.CreateSendOnly(name)
            : RawEndpointConfiguration.Create(name, (c, d) => onMessage(c, typedScenarioContext, d), "poison");

        config.AutoCreateQueue();
        configure(config);
        return(Task.FromResult <ComponentRunner>(new Runner(config, name, sendOnly,
                                                            endpoint => onStarting != null ? onStarting(endpoint, typedScenarioContext) : Task.FromResult(0),
                                                            endpoint => onStarted != null ? onStarted(endpoint, typedScenarioContext) : Task.FromResult(0))));
    }
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            var endpointConfiguration = RawEndpointConfiguration.Create(attribute.QueueName, OnMessage, poisonMessageQueue);

            if (Directory.Exists(nsbLogPath))
            {
                LogManager.Use <DefaultFactory>().Directory(nsbLogPath);
            }

            var connectionString = AmbientConnectionStringProvider.Instance.GetConnectionString(attribute.Connection);

            endpointConfiguration.UseTransport <AzureStorageQueueTransport>().ConnectionString(connectionString);

            endpointConfiguration.DefaultErrorHandlingPolicy(poisonMessageQueue, immediateRetryCount);

            endpoint = await RawEndpoint.Start(endpointConfiguration).ConfigureAwait(false);
        }
Beispiel #16
0
    static async Task <List <MessageContext> > RunResubscriberOnce(params MessageContext[] input)
    {
        await Cleanup("ResubscriberTest", "ResubscriberTest.Resubscriber", "poison").ConfigureAwait(false);

        var done           = new TaskCompletionSource <bool>();
        var output         = new List <MessageContext>();
        var listenerConfig = RawEndpointConfiguration.Create("ResubscriberTest", (context, dispatcher) =>
        {
            if (context.Headers[Headers.SubscriptionMessageType] == "stop")
            {
                done.SetResult(true);
            }
            else
            {
                output.Add(context);
            }

            return(Task.CompletedTask);
        }, "poison");

        listenerConfig.UseTransport <MsmqTransport>();
        listenerConfig.LimitMessageProcessingConcurrencyTo(1);
        listenerConfig.AutoCreateQueue();

        var listener = await RawEndpoint.Start(listenerConfig);

        var resubscriber = await Resubscriber <MsmqTransport> .Create("ResubscriberTest", TimeSpan.FromSeconds(3), t => { });

        foreach (var messageContext in input)
        {
            await resubscriber.InterceptMessageForwarding("ResubscriberTest", messageContext, NoopDispatch, NoopForward).ConfigureAwait(false);
        }

        await resubscriber.InterceptMessageForwarding("ResubscriberTest", CreateTerminatingContext(), NoopDispatch, NoopForward);

        await resubscriber.Start();

        await done.Task;

        await resubscriber.Stop();

        await listener.Stop();

        return(output);
    }
Beispiel #17
0
        Resubscriber(string inputQueueName, TimeSpan delay, Action <TransportExtensions <T> > configureTransport)
        {
            this.inputQueueName = inputQueueName;
            config = RawEndpointConfiguration.Create(inputQueueName + ".Resubscriber",
                                                     async(context, dispatcher) =>
            {
                context.Headers.TryGetValue(Headers.SubscriptionMessageType, out var messageTypeString);
                if (!context.Headers.TryGetValue(Headers.SubscriberTransportAddress, out var subscriberAddress))
                {
                    subscriberAddress = context.Headers[Headers.ReplyToAddress];
                }

                var key = Tuple.Create(subscriberAddress, messageTypeString);
                var resubscriptionId        = context.Headers[ResubscriptionIdHeader];
                var resubscriptionTimestamp = DateTime.Parse(context.Headers[ResubscriptionTimestampHeader]);
                if (idMap.ContainsKey(key))
                {
                    var valuePair = idMap[key];
                    if (valuePair.Item1 != resubscriptionId && resubscriptionTimestamp < valuePair.Item2)
                    {
                        //If we already processed a newer subscribe/unsubscribe message for this -> ignore
                        return;
                    }
                    //We've seen that same message before. Let's pause the resubscription for some time
                    await Task.Delay(delay);
                }

                //Send it to the bridge to re-subscribe
                var outgoingMessage = new OutgoingMessage(context.MessageId, context.Headers, context.Body);
                var operation       = new TransportOperation(outgoingMessage, new UnicastAddressTag(inputQueueName));

                await dispatcher.Dispatch(new TransportOperations(operation), context.TransportTransaction, context.Extensions).ConfigureAwait(false);
                logger.Debug("Moved subscription message back to the queue.");
                idMap[key] = Tuple.Create(resubscriptionId, resubscriptionTimestamp);
            }, "poison");
            config.AutoCreateQueue();
            config.LimitMessageProcessingConcurrencyTo(1);
            var transport = config.UseTransport <T>();

            configureTransport(transport);
        }
Beispiel #18
0
    static async Task Start()
    {
        #region Configuration

        var senderConfig = RawEndpointConfiguration.Create(
            endpointName: "EndpointName",
            onMessage: OnMessage,
            poisonMessageQueue: "error");
        senderConfig.UseTransport <MsmqTransport>();

        var sender = await RawEndpoint.Start(senderConfig)
                     .ConfigureAwait(false);

        #endregion

        #region Sending

        var body    = Serialize();
        var headers = new Dictionary <string, string>
        {
            ["SomeHeader"] = "SomeValue"
        };
        var request = new OutgoingMessage(
            messageId: Guid.NewGuid().ToString(),
            headers: headers,
            body: body);

        var operation = new TransportOperation(
            request,
            new UnicastAddressTag("Receiver"));

        await sender.SendRaw(
            outgoingMessages : new TransportOperations(operation),
            transaction : new TransportTransaction(),
            context : new ContextBag())
        .ConfigureAwait(false);

        #endregion
    }
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            var nameShortener            = new RuleNameShortener();
            var endpointConfigurationRaw = RawEndpointConfiguration.Create(_attribute.Endpoint, OnMessage, _poisonMessageQueue);

            if (_nServiceBusOptions.EndpointConfiguration != null)
            {
                endpointConfigurationRaw = _nServiceBusOptions.EndpointConfiguration.Invoke(endpointConfigurationRaw);
            }
            else
            {
                var tokenProvider = TokenProvider.CreateManagedIdentityTokenProvider();

                endpointConfigurationRaw.UseTransport <AzureServiceBusTransport>()
                .RuleNameShortener(nameShortener.Shorten)
                .CustomTokenProvider(tokenProvider)
                .ConnectionString(_attribute.Connection)
                .Transactions(TransportTransactionMode.ReceiveOnly);
            }

            if (!string.IsNullOrEmpty(EnvironmentVariables.NServiceBusLicense))
            {
                endpointConfigurationRaw.UseLicense(EnvironmentVariables.NServiceBusLicense);
            }
            endpointConfigurationRaw.DefaultErrorHandlingPolicy(_poisonMessageQueue, ImmediateRetryCount);
            endpointConfigurationRaw.AutoCreateQueue();

            _endpoint = await RawEndpoint.Start(endpointConfigurationRaw).ConfigureAwait(false);

            if (_nServiceBusOptions.OnStarted != null)
            {
                _nServiceBusOptions.OnStarted.Invoke(_endpoint);
            }

            await _endpoint.SubscriptionManager.Subscribe(_parameter.ParameterType, new ContextBag());
        }
        public WebOutboxConfiguration(string outboxEndpointName, string destinationEndpointName, string poisonMessageQueue)
        {
            _outboxEndpointConfiguration = new EndpointConfiguration(outboxEndpointName);

            _outboxEndpointConfiguration.Pipeline.Replace(
                "UnicastSendRouterConnector",
                new UnicastSendRouterConnector(_unicastRoutingTable, outboxEndpointName),
                "Routes all messages to the outbox endpoint");

            _outboxEndpointConfiguration.Pipeline.Replace(
                "OutgoingPhysicalToRoutingConnector",
                new OutgoingPhysicalToRoutingConnector(outboxEndpointName),
                "Routes all messages to the outbox endpoint");

            _outboxEndpointConfiguration.SendOnly();

            _forwarderEndpointConfiguration = RawEndpointConfiguration.Create(
                endpointName: outboxEndpointName,
                onMessage: (context, messages) => _onMessage?.Invoke(context, messages),
                poisonMessageQueue: poisonMessageQueue);

            _destinationEndpointName          = destinationEndpointName;
            _destinationEndpointConfiguration = RawEndpointConfiguration.CreateSendOnly(destinationEndpointName);
        }
Beispiel #21
0
        private static async Task Main(string[] args)
        {
            var maxConcurrency = 10;

            var channel = Channel.CreateBounded <MessageContext>(new BoundedChannelOptions(maxConcurrency)
            {
                SingleReader = true,
                SingleWriter = false,
                AllowSynchronousContinuations = false,
                FullMode = BoundedChannelFullMode.Wait
            });

            var senderConfig =
                RawEndpointConfiguration.Create("Sender", (ctx, disp) => OnMessage(ctx, disp, channel), "error");

            senderConfig.AutoCreateQueue();
            var transport = senderConfig.UseTransport <RabbitMQTransport>();

            transport.UseConventionalRoutingTopology();
            transport.ConnectionString("host=localhost");
            senderConfig.LimitMessageProcessingConcurrencyTo(maxConcurrency);

            async Task ReadChannel(Channel <MessageContext> channel)
            {
                var contexts = new List <MessageContext>(maxConcurrency);

                await Console.Error.WriteLineAsync("Start reading...");

                while (await channel.Reader.WaitToReadAsync().ConfigureAwait(false))
                {
                    try
                    {
                        await Console.Error.WriteLineAsync("Start TryRead...");

                        while (channel.Reader.TryRead(out var context))
                        {
                            contexts.Add(context);
                        }

                        await Console.Error.WriteLineAsync($"Got pushed {contexts.Count} contexts.");

                        foreach (var context in contexts)
                        {
                            await Console.Error.WriteAsync(".");

                            context.Extensions.Get <TaskCompletionSource <bool> >().TrySetResult(true);
                        }
                    }
                    finally
                    {
                        contexts.Clear();
                    }

                    await Console.Error.WriteLineAsync("Done TryRead...");
                }

                await Console.Error.WriteLineAsync("Done reading...");
            }

            var readerTask = Task.Run(() => ReadChannel(channel));

            var sender = await RawEndpoint.Start(senderConfig).ConfigureAwait(false);

            var cts = new CancellationTokenSource();

            async Task ProduceMessages(IReceivingRawEndpoint sender, CancellationToken cancellationToken)
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    var transportOperations = new TransportOperation[15];
                    for (var i = 0; i < 15; i++)
                    {
                        var headers = new Dictionary <string, string> {
                            ["SomeHeader"] = "SomeValue"
                        };
                        var request = new OutgoingMessage(Guid.NewGuid().ToString(), headers, Array.Empty <byte>());

                        transportOperations[i] = new TransportOperation(request, new UnicastAddressTag("Sender"));
                    }

                    await sender.Dispatch(
                        new TransportOperations(transportOperations),     //Can have multiple sends in one batch
                        new TransportTransaction(),
                        new ContextBag())
                    .ConfigureAwait(false);

                    try
                    {
                        await Task.Delay(100, cts.Token);
                    }
                    catch (OperationCanceledException)
                    {
                        // ignored
                    }
                }

                await Console.Error.WriteLineAsync("Stopped sending");
            }

            var senderTask = Task.Run(() => ProduceMessages(sender, cts.Token), CancellationToken.None);

            await Console.Error.WriteLineAsync("Press any key to stop");

            await Console.In.ReadLineAsync();

            cts.CancelAfter(TimeSpan.FromSeconds(15));
            await sender.StopReceiving();

            channel.Writer.Complete();
            await readerTask;

            await senderTask;
        }