zeromq / netmq

A 100% native C# implementation of ZeroMQ for .NET
Other
2.96k stars 744 forks source link

[crash]: "Cannot close an uninitialised Msg" exception in NetMQ.Msg.Close() #975

Open elmerjfudd opened 3 years ago

elmerjfudd commented 3 years ago

Environment

NetMQ Version: d297a27 Operating System: Windows 10 .NET Version: net47

Expected behaviour

NetMQ.Tests.NetMQQueueTests.EnqueueShouldNotBlockWhenCapacityIsZero passes without triggering any assert operations.

Actual behaviour

We observe the following exception when running NetMQ.Tests.NetMQQueueTests.EnqueueShouldNotBlockWhenCapacityIsZero:

Failed NetMQ.Tests.NetMQQueueTests.EnqueueShouldNotBlockWhenCapacityIsZero [1 s]
Error Message:
 NetMQ.FaultException : Cannot close an uninitialised Msg.
Stack Trace:
   at NetMQ.Msg.Close() in ../src/NetMQ/Msg.cs:line 441
 at NetMQ.NetMQQueue`1.Dispose() in ../src/NetMQ/NetMQQueue.cs:line 160
 at NetMQ.Tests.NetMQQueueTests.EnqueueShouldNotBlockWhenCapacityIsZero() in E:\GitBench\BugRepos\netmq\src\NetMQ.Tests\NetMQQueueTests.cs:line 51

Poking around, looks like a race between NetMQ.NetMQQueue.Dispose() and NetMQ.NetMQQueue.Dequeue(). Most likely, NetMQ.Msg.Close() is called twice on m_dequeueMsg.

Steps to reproduce the behaviour

Originally, the bug was happeneing spuriously, but we can enhance the issue by slightly modifying the original test in NetMQQueueTests.cs (changes marked as comments below):

[Fact]
public void EnqueueShouldNotBlockWhenCapacityIsZero2()
{
    using (var mockSocket = new PairSocket())
    using (var queue = new NetMQQueue<int>())
    {
        int socketWatermarkCapacity = mockSocket.Options.SendHighWatermark + mockSocket.Options.ReceiveHighWatermark;

        Task task1 = Task.Run(() =>
        {
            for (int i = 0; i < socketWatermarkCapacity + 10000; i++)   // <--- increased loop count
            {
                queue.Enqueue(i);
            }
        });

        Task task2 = Task.Run(() =>  // <--- running a set of Dequeue() ops concurrently with Enqueue() ops and the Dispose() at the end of the "using" block
        {
            for (int i = 0; i < socketWatermarkCapacity + 10000; i++)    // <--- increased loop count
            {
                queue.Dequeue();
            }
        });

        bool completed = task2.Wait(TimeSpan.FromSeconds(1));
    }
}
elmerjfudd commented 3 years ago

Looks like this data race has similar side effects as #930. Maybe the root causes are related?

EDIT: Just to clarify, it looks like #933 does not address the root cause of this issue.

elmerjfudd commented 2 years ago

This issue still occurs with the most recent commit, i.e., d934760 I was wondering if there is any fix currently in the works.

follesoe commented 1 year ago

Facing the same issue on Xamarin.iOS, and Xamasrin.Android.

elmerjfudd commented 1 year ago

Just checking in, because it looks like this is still happening in the latest stable version.

vbergara commented 1 year ago

I'm still facing this issue in the latest version. Edit: is it really necessary to throw an exception when Close is invoked on an uninitialized Msg?

getarobo commented 1 year ago

Please!!!! need help on this!!!!! ANY IDEAS?

guipalazzo commented 9 months ago

I'm still facing this issue in the latest version.

follesoe commented 9 months ago

Same...

getarobo commented 9 months ago

I have been using pub and sub in a same thread <-- this caused "Cannot close an uninitialised Msg" sometimes.

Now I run subscriber and publisher on different thread. I have no longer seen such error.

something like this (ran on unity), hope this helps

// sockets
        public PublisherSocket _pubSocket;
        public SubscriberSocket _subSocket;

        private readonly ConcurrentQueue<ZmqMsg> _msgRecvQueue = new ConcurrentQueue<ZmqMsg>();
        private BlockingCollection<NetMQMessage> _toSendQueue = new BlockingCollection<NetMQMessage>()

        private Thread _subThread;
        private Thread _pubThread;
...

// start
            AsyncIO.ForceDotNet.Force();
            NetMQConfig.Linger = new TimeSpan(0, 0, 0);

            _pubThread = new Thread(_PubWorkerThread);
            _pubThread.Start();

            _subThread = new Thread(_SubWorkerThread);
            _subThread.Start();
LiorBanai commented 6 months ago

Still exists in latest version

aalfath commented 6 months ago

I can confirm that this still exists in the latest version.