2

I am using .Net version 4.6.1 and VS2015 Update 3.

I am facing a weird issue as a piece of code is working fine when compiled in Debug mode, however, it fails with NullReferenceException when compiled in Release mode.

I have an async method which uses TaskCompletionSource to wait for task completion and I am again retrying after 5 seconds if response is not received. I have reproduced this error in the simplified sample code given below.

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TaskCompletionIssue
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
        #if DEBUG
            this.Text = "Running in Debug Mode";
        #else
            this.Text = "Running in Release Mode";
        #endif
        }

        private async void btnStartTest_Click(object sender, EventArgs e)
        {
            try
            {
                this.Cursor = Cursors.WaitCursor;
                await sendRequest("Test");
                MessageBox.Show("Test Completed Successfully");
            }
            finally
            {
                this.Cursor = Cursors.Default;
            }
        }

        private static TimeSpan secondsToWaitBeforeRetryingRequest = TimeSpan.FromSeconds(5);
        private static TimeSpan secondsToWaitForResponse = TimeSpan.FromSeconds(180);
        internal static readonly ConcurrentDictionary<Guid, TaskCompletionSource<object>> ClientResponses = new ConcurrentDictionary<Guid, TaskCompletionSource<object>>();
        private static Thread t1 = null;

        public async static Task<object> sendRequest(String req)
        {
            var tcs = new TaskCompletionSource<object>();
            Guid requestId = Guid.NewGuid();

            ClientResponses.TryAdd(requestId, tcs);

            try
            {
                DateTime startTime = DateTime.Now;
                while (true)
                {
                    //Call method to send request, It doesn't block the thread
                    SendRequestForProcessing(requestId, req);
                    if (tcs == null)
                    {
                        MessageBox.Show("tcs is null");
                    }

                    var task = tcs.Task;

                    //Wait for the client to respond        
                    if (await Task.WhenAny(task, Task.Delay(secondsToWaitBeforeRetryingRequest)) == task)
                    {
                        return await task;
                    }
                    else
                    {
                        if ((DateTime.Now - startTime).TotalSeconds > secondsToWaitForResponse.TotalSeconds)
                        {
                            throw new TimeoutException("Could not detect response within " + secondsToWaitForResponse.TotalSeconds.ToString() + " secs.");
                        }
                        else
                        {
                            //Let's try again, Previous call might be lost due to network issue
                        }
                    }
                }
            }
            finally
            {
                // Remove the tcs from the dictionary so that we don't leak memory
                ClientResponses.TryRemove(requestId, out tcs);
            }
        }

        private static void SendRequestForProcessing(Guid requestId, string req)
        {
            //Not doing anything with request as this is just a sample program
            if (t1 == null || !t1.IsAlive)
            {
                t1 = new Thread(receivedResponse);
                t1.Name = "Test";
                t1.IsBackground = true;
                t1.Start(requestId);
            }
        }

        public static void receivedResponse(object id)
        {
            TaskCompletionSource<object> tcs;
            Guid requestId = (Guid)id;
            if (ClientResponses.TryGetValue(requestId, out tcs))
            {
                //Some static wait in sample program
                Thread.Sleep(TimeSpan.FromSeconds(15));

                // Trigger the task continuation
                tcs.TrySetResult("Test Success");
            }
            else
            {
                throw new Exception($"Request not found for id {requestId.ToString()}");
            }
        }
    }
}

In the above code sample, I have shown a message if variable 'tcs' becomes null. I get the error message when the code is compiled in Release mode; nonetheless, it works fine in Debug mode.

Furthermore, To fix this issue, I have simply moved the below line of code outside the try block and everything is working fine.

var task = tcs.Task;

It appears some kind of .Net bug to me.

Can anyone please help me to understand this awkward behavior?

Edit 1:

Well, this issue is kind of hard to believe, thus, I have created a working sample project reproducing this issue. Please download it from the below link and compile the code in both Debug and Release mode.

Download Sample

Download Executables

After the compilation, please run the executable file in both the modes one by one.

In Debug mode, the test should complete after 15 seconds, however, it will show a message that 'tcs' variable is null in release mode and on pressing Ok button it will throw a NullReferenceException.

prem
  • 3,348
  • 1
  • 25
  • 57
  • I dispute your claim that the code above demonstrates the problem you describe. I've compiled and run it, and even the Release build fails to reproduce any NRE. The code does have a race, between the time that the `sendRequest()` method's loop eventually gives up and the thread method `receivedResponse()` retrieving the TCS. But for sure, the `sendRequest()` method will never see `tcs` as null. _"It appears some kind of .Net bug to me."_ -- .NET is certainly not 100% bug-free. But it is exceedingly unlikely that you have come across one here. Look closely at your own code. – Peter Duniho Nov 11 '19 at 18:14
  • I just came to know that It is not getting reproduced in VS2019, however it is reproducing in VS2015 Update 3 which I have. I have attached a link to compiled code exe files. Just check it as issue is easily reproducible. – prem Nov 12 '19 at 10:21
  • I doubt you'll find anyone who has any interest in downloading compiled assemblies. The whole point of Stack Overflow is to answer _programming_ questions. Problems need to be reproducible with _code_. Once the code's compiled, it becomes a user support question, off-topic on SO. Since you can reproduce the problem, I suggest you fix your race condition and see if the problem goes away. If that doesn't work, then follow the advice in the marked duplicate for tracking down and diagnosing `NullReferenceException`. – Peter Duniho Nov 12 '19 at 16:42
  • Indeed, my problem is not related to code as it is working perfectly fine in Debug mode. The problem occurs only after code compilation, so it is related to compiler. I have posted this issue on msdn forum also and after analyzing the code and compiled assemblies they agreed that there is some issue. Besides, they have suggested to post it at C# github repo where compiler team can look into it. I will post the answer once I get something from compiler team. Could you please remove that duplicate Null reference tag from question as that question has nothing to do with the scenario I am facing. – prem Nov 13 '19 at 13:17
  • _"is not related to code as it is working perfectly fine in Debug mode"_ -- that is not a logical conclusion. There are lots of types of bugs _in code_ that only manifest in one type of build or another. Threading/concurrency bugs are one of the biggest examples of this, since they are so sensitive to timing, memory usage, and optimizations. _"they have suggested to post it at C# github repo where compiler team can look into it"_ -- "they"? I don't know who that "they" is. But since you take so much stock in their statements, I recommend you ask "them" for help with your bug. – Peter Duniho Nov 13 '19 at 16:58
  • "*I've compiled and run it, and even the Release build fails to reproduce any NRE*"-- Well, I am believing your words as I don't have any other version of Visual Studio and the same is confirmed by one more user on MSDN that it is not getting reproduced in VS2019. You could have reproduced it with the above code if you would have compiled it on VS2015 Update 3(version 14.0.25420.01). With "they", I am referring to MSDN C# forum moderators and the one answering my question has 120K reputation , so I can believe his findings. – prem Nov 14 '19 at 06:42
  • Here's the [link](https://social.msdn.microsoft.com/Forums/vstudio/en-US/bcce434a-25cb-4a45-918c-a630e1debabd/taskcompletionsource-object-becomes-null-in-async-method-when-code-is-compiled-in-release-mode?forum=csharpgeneral) for my question posted at MSDN C# forum. – prem Nov 14 '19 at 06:43

0 Answers0