- 
                Notifications
    You must be signed in to change notification settings 
- Fork 613
Closed
Description
Hi,
one test in our code base - that was previously working - doesn't work anymore. N messages were published - correctly - over the MQ and then consumed by a consumer components (the component under test). After upgrading RabbitMQ and also the client the messages were not published anymore. Did the #878 introduced a change? Am I doing something wrong?
Expectations: all messages are published and confirmed and available to be consumed.
Results: few messages are published and confirmed, few messages are not confirmed and many get exceptions on publish.
Here the tests:
using NUnit.Framework;
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
    public class RabbitMqTests
    {
        [Test]
        public async Task TestMultithreadPublishWithBarrier()
        {
            var count = 500;
            var barrier = new AsyncBarrier(count);
            var exchangeName = nameof(TestMultithreadPublishWithBarrier);
            var factory = new ConnectionFactory
            {
                UserName = "rabbitmq",
                Password = "rabbitmq",
                HostName = "localhost",
                Port = 5672,
                VirtualHost = "/",
            };
            var connection = factory.CreateConnection(nameof(TestMultithreadPublishWithBarrier));
            var tasksList = new List<Task<(int Code, string? Error)>>();
            using (var channel = connection.CreateModel())
            {
                channel.ExchangeDeclare(exchangeName, ExchangeType.Topic, true);
                channel.ConfirmSelect();
                for (int i = 0; i < count; i++)
                {
                    var task = PublishAsync(channel, $"Message {i}", exchangeName, $"rabbit.test.{i}", barrier);
                    tasksList.Add(task);
                }
                await Task.WhenAll(tasksList);
                Console.Error.WriteLine($"# publishing error: {tasksList.Where(t => t.Result.Code == 1).Count()}");
                Console.Error.WriteLine($"# confirms error: {tasksList.Where(t => t.Result.Code == 2).Count()}");
                Console.Error.WriteLine($"# nack-ed: {tasksList.Where(t => t.Result.Code == 3).Count()}");
                Console.Error.WriteLine($"# not confirmed: {tasksList.Where(t => t.Result.Code == 4).Count()}");
                Console.Error.WriteLine($"# publishing error: {tasksList.Where(t => t.Result.Code == 1).First().Result.Error}");
                Assert.IsTrue(tasksList.Where(t => t.Result.Code == 0).Count() == count);
            }
        }
        [Test]
        public async Task TestMultithreadPublish()
        {
            var count = 500;
            var exchangeName = nameof(TestMultithreadPublish);
            var factory = new ConnectionFactory
            {
                UserName = "rabbitmq",
                Password = "rabbitmq",
                HostName = "localhost",
                Port = 5672,
                VirtualHost = "/",
            };
            var connection = factory.CreateConnection(nameof(TestMultithreadPublish));
            using (var channel = connection.CreateModel())
            {
                channel.ExchangeDeclare(exchangeName, ExchangeType.Topic, true);
                channel.ConfirmSelect();
                var tasksList = new List<Task<(int Code, string? Error)>>();
                for (int i = 0; i < count; i++)
                {
                    var task = Task.Run(() => Publish(channel, $"Message {i}", exchangeName, $"rabbit.test.{i}"));
                    tasksList.Add(task);
                }
                await Task.WhenAll(tasksList);
                Console.Error.WriteLine($"# publishing error: {tasksList.Where(t => t.Result.Code == 1).Count()}");
                Console.Error.WriteLine($"# confirms error: {tasksList.Where(t => t.Result.Code == 2).Count()}");
                Console.Error.WriteLine($"# nack-ed: {tasksList.Where(t => t.Result.Code == 3).Count()}");
                Console.Error.WriteLine($"# not confirmed: {tasksList.Where(t => t.Result.Code == 4).Count()}");
                Assert.IsTrue(tasksList.Where(t => t.Result.Code == 0).Count() == count);
            }
        }
        private async Task<(int Code, string? Error)> PublishAsync(IModel channel, string message, string exchangeName, string routingKey, AsyncBarrier barrier)
        {
            await barrier.SignalAndWaitAsync();
            return Publish(channel, message, exchangeName, routingKey);
        }
        private (int Code, string? Error) Publish(IModel channel, string message, string exchangeName, string routingKey)
        {
            byte[] byteMessage = Encoding.UTF8.GetBytes(message);
            IBasicProperties props = channel.CreateBasicProperties();
            props.ContentType = "application/json";
            props.Persistent = true;
            bool isConfirmed;
            bool timedOut;
            lock (channel)
            {
                try
                {
                    channel.BasicPublish(exchangeName, routingKey, props, byteMessage);
                }
                catch (Exception e)
                {
                    return (1, $"{message}: {e.ToString()}");
                }
                try
                {
                    isConfirmed = channel.WaitForConfirms(new TimeSpan(0, 1, 0), out timedOut);
                }
                catch (Exception e)
                {
                    return (2, $"{message}: {e.ToString()}");
                }
            }
            if (!isConfirmed)
            {
                return (3, $"{message}: got NACK-ed");
            }
            if (timedOut)
            {
                return (4, $"{message}: got time-out on waiting for message confirmaiton");
            }
            return (0, null);
        }
        private class AsyncBarrier
        {
            private int _count;
            private TaskCompletionSource<bool> _tcs;
            private int _waiting;
            public AsyncBarrier(int count)
            {
                _count = count;
                _waiting = 0;
                // Compiler doesn't recognize the initialization in a method...
                _tcs = new TaskCompletionSource<bool>();
            }
            public Task SignalAndWaitAsync()
            {
                var tcs = _tcs;
                if (Interlocked.Increment(ref _waiting) == _count)
                {
                    // Reset();
                    tcs.SetResult(true);
                }
                return tcs.Task;
            }
            private void Reset()
            {
                _waiting = 0;
                _tcs = new TaskCompletionSource<bool>();
            }
        }
    }
RabbitMQ instance
version: '2.4'
services:
  mq:
    image: rabbitmq:3.8.14-management-alpine
    ports:
    - '5672:5672'
    - '15672:15672'
    environment:
         RABBITMQ_NODENAME=eventStoreMq
        RABBITMQ_DEFAULT_USER=rabbitmq
        RABBITMQ_DEFAULT_PASS=rabbitmq
    healthcheck:
      test: ["CMD-SHELL", "rabbitmq-diagnostics -q ping"]
      interval: 1s
      timeout: 5s
      retries: 5
RabbitMQ logs
mq_1                | 2021-04-13 18:49:07.840 [info] <0.1050.0> Connection <0.1050.0> (172.19.0.1:42592 -> 172.19.0.2:5672) has a client-provided name: TestConcurrentPublish
mq_1                | 2021-04-13 18:49:07.845 [info] <0.1050.0> connection <0.1050.0> (172.19.0.1:42592 -> 172.19.0.2:5672 - TestConcurrentPublish): user 'rabbitmq' authenticated and granted access to vhost '/'
mq_1                | 2021-04-13 18:54:07.849 [error] <0.1050.0> closing AMQP connection <0.1050.0> (172.19.0.1:42592 -> 172.19.0.2:5672 - TestConcurrentPublish):
mq_1                | missed heartbeats from client, timeout: 60s
mq_1                | 2021-04-13 18:54:07.849 [info] <0.1462.0> Closing all channels from connection '172.19.0.1:42592 -> 172.19.0.2:5672' because it has been closed
dotnet --info
.NET SDK (reflecting any global.json):
 Version:   5.0.202
 Commit:    db7cc87d51
Runtime Environment:
 OS Name:     ubuntu
 OS Version:  18.04
 OS Platform: Linux
 RID:         ubuntu.18.04-x64
 Base Path:   /snap/dotnet-sdk/120/sdk/5.0.202/
Host (useful for support):
  Version: 5.0.5
  Commit:  2f740adc14
.NET SDKs installed:
  5.0.202 [/snap/dotnet-sdk/120/sdk]
.NET runtimes installed:
  Microsoft.AspNetCore.App 5.0.5 [/snap/dotnet-sdk/120/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 5.0.5 [/snap/dotnet-sdk/120/shared/Microsoft.NETCore.App]
To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download
Metadata
Metadata
Assignees
Labels
No labels