Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test passes in debug build but fails in release build

I have written a unit test for an asynchronous class member. The test passes as expected when I execute my test under "Debug build". However, it hangs the CPU (while loop is deadlocked) when I execute my test under "Release build".

If I specifically configure the Unit test project with Debug build (that is Debug build unit test assembly and Release build target assembly), the test passes as well.

Code to Test

    public override void DoSomething(object parameter)
    {
        ThreadPool.QueueUserWorkItem(AsyncDoSomething, parameter);
    }

    private void AsyncDoSomething(object parameter)
    {
        //Doing something
                .....

        //Something is done
        RaiseSomethingIsDone();
    }

My Unit Test

    public void DoingSomethingTest()
    {
        bool IsSomethingDone = false;

        //Setup
        //Doing some setup here.
        target.SomethingDone += (sender, args) =>
            {
                IsSomethingDone = true;
            };

        //Exercise
        target.DoSomething(_someParameter);
        while (!IsSomethingDone ){}

        //Verify
        //Doing some asserts here.
    }

Here is the IL generated by the C# compiler under Debug configuration and Release configuration:

Debug while loop IL interpenetration:

  IL_00cb:  ldloc.s    'CS$<>8__locals7'
  IL_00cd:  ldfld      bool IsSomethingDone
  IL_00d2:  ldc.i4.0
  IL_00d3:  ceq
  IL_00d5:  stloc.s    CS$4$0001
  IL_00d7:  ldloc.s    CS$4$0001
  IL_00d9:  brtrue.s   IL_00c9

Release while loop IL interpenetration:

  IL_00bc:  ldloc.s    'CS$<>8__locals7'
  IL_00be:  ldfld      bool IsSomethingDone
  IL_00c3:  brfalse.s  IL_00bc

I understand there are better ways to synchronize the test thread to the background ThreadPool thread.

My questions are

  1. why is the release build not working? The flag IsSomethingDone is not set by the worker thread.

  2. Is it because the eventhandler (the lambda expression) does not get executed?

  3. Is the event not raised correctly?

By the way, I verified that DoSomething is executed correctly and generated the correct result.

Follow up Questions:

  1. Should one build unit test projects under Debug Build or Release Build ?

  2. Should one test the target assembly under Debug Build or Release Build ?

like image 260
Frank Liu Avatar asked Dec 10 '13 01:12

Frank Liu


People also ask

What is the difference between Release build and debug build?

By default, Debug includes debug information in the compiled files (allowing easy debugging) while Release usually has optimizations enabled. As far as conditional compilation goes, they each define different symbols that can be checked in your program, but they are language-specific macros.

Can you debug a Release build?

You can now debug your release build application. To find a problem, step through the code (or use Just-In-Time debugging) until you find where the failure occurs, and then determine the incorrect parameters or code.

Is Release build faster than debug?

Debug Mode: In debug mode the application will be slow. Release Mode: In release mode the application will be faster. Debug Mode: In the debug mode code, which is under the debug, symbols will be executed. Release Mode: In release mode code, which is under the debug, symbols will not be executed.

Can you use breakpoints in Release mode?

You can then put a breakpoint on that int declaration line, and it'll be hit, even in release mode. Then just step up the stack in the debugger back to the function you actually wanted a breakpoint in.


1 Answers

The flag IsSomethingDone is being cached in a CPU register, and never reloaded from memory, so that when one thread modifies it, the other never sees the modified value.
This is an optimization which the .NET JIT compiler does, so you'd need to look at the actual x86/x64 binary dissasembly to see this, MSIL is not deep enough :-)

The solution is to mark your flag as volatile

This is a textbook example of one of the kind of multithreading bugs that can occur. So much so, that here's a link to part of a presentation I did at TechEd New Zealand 2012 on this exact subject. Hopefully it should explain what's going on in more detail

http://orionedwards.blogspot.co.nz/2012/09/teched-2012-background-2-compiler.html

As you have mentioned, you can't put volatile on a local variable, but you should be able to use Thread.VolatileRead instead.

I'd suggest not using this design at all - your main thread is going to spin using 100% CPU until your worker completes, which is horrible. A better solution is to use a ManualResetEvent (or other kind of signaling mechanism).

Here's an example

public void DoingSomethingTest()
{
    var done = new ManualResetEvent(false); // initially not set

    //Setup
    //Doing some setup here.
    target.SomethingDone += (sender, args) =>
        {
            done.Set();
        };

    //Exercise
    target.DoSomething(_someParameter);
    done.WaitOne(); // waits until Set is called. You can specify an optional timeout too which is nice

    //Verify
    //Doing some asserts here.
}
like image 135
Orion Edwards Avatar answered Nov 04 '22 22:11

Orion Edwards