Typemock Racer adds deadlock detection to unit testing

 

Tuesday, July 14. 2009 - MM

 

Using more than one thread in a program is a common way to write high-performance applications which take advantage of current multi-core cpus. Long running and resource intensive tasks are split in several parts and distributed among available cores to benefit from this modern hardware. But the usage of a multi-threaded structure introduces more complexity into program code which also complicates the implementation of unit tests. Especially the detection of deadlocks is a burden for programmers because the necessary test code is often even more complex than the actual program code.


This is where Typemock Racer tries to fill a gap in modern unit testing. There are already several reviews for this tool which explain the basic usage and how deadlocks in multi-threaded programs can be found. This review goes one step further and shows how Typemock Isolator can support Typemock Racer in finding deadlocks when the control flow of a thread is not deterministic. Hence the following example uses two locks which are acquired in either different or the same order depending on the result of a non-deterministic statement.

 

Assuming that a Service instance uses a Worker object to perform a long running task. In the Service's Serve method the Worker object is created and that object's Work method is executed in a separate thread:

 

        public void Serve()
        {
            //Create worker and start long running work in new thread
            Worker worker = new Worker(this);
            Thread workerThread = new Thread(worker.Work);
            workerThread.Start();

            //Change object state with lock order A - B
            ChangeObjectStateWithLockOrderAB();
        }

 

After the starting of the new thread the Service object calls a method which changes its state. The Worker's Work method does its long running work and uses a non-deterministic statement which has an impact on which notification method of the Service object will be called:

 

       internal void Work()
        {
            //Do long running work

            //Notify service
            if (DateTime.Now.Ticks % 2 == 0)
                _service.ChangeObjectStateWithLockOrderAB();
            else
                _service.ChangeObjectStateWithLockOrderBA();
        }

 

Both notification methods acquire two locks before the state of the Service object is changed whereas the first one is also used by the Service object itself:

 

      internal void ChangeObjectStateWithLockOrderAB()
        {
            lock (_lockA)
            {
                lock (_lockB)
                {
                    //Change object state  
                }
            }
        }

        internal void ChangeObjectStateWithLockOrderBA()
        {
            lock (_lockB)
            {
                lock (_lockA)
                {
                    //Change object state
                }
            }
        }

 

To check whether the execution of the Service's Serve method leads to a deadlock the following test method can be fired:

 

        [TestMethod, Isolated]
        public void CheckForDeadlock()
        {
            Service service = new Service();
            ThreadTest
                .AddThreadAction(service.Serve)
                .Start();
        }

 

The unit test framework executes the CheckForDeadlock method and Typemock Racer discovers that two threads and two locks are involved in this test. Hence a total of four scenarios are executed by Typemock Racer. Only scenarios in which the acquisition of locks of both threads are interwoven are candidates for the deadlock analysis. The following table describes the scenarios and how they should behave:

 

 

Scenario

Analysis

Result

1

Thread1->acquire lock 1 – Thread2->acquire lock 1 – Thread1->acquire lock 2 – Thread2->acquire lock 2

Acquisition of locks in same order – no deadlock

2

Thread1->acquire lock 1 – Thread2->acquire lock 2 – Thread1->acquire lock 2 – Thread2->acquire lock 1

Acquisition of locks in different order – deadlock detected

3

Thread1->acquire lock 2 – Thread2->acquire lock 2 – Thread1->acquire lock 1 – Thread2->acquire lock 1

Acquisition of locks in same order – no deadlock

4

Thread1->acquire lock 2 – Thread2->acquire lock 1 – Thread1->acquire lock 1 – Thread2->acquire lock 2

Acquisition of locks in different order – deadlock detected

 

For Thread1, the main thread, lock 1 always means that a lock on object _lockA is acquired and lock 2 acquires _lockB. But for Thread2, the worker thread, the relationship between the analysis lock and the real lock is not fixed. As the condition in the Worker's if statement is not deterministic it is possible that all four scenarios detect a deadlock. Even worse there can be test runs in which none of the scenarios can detect a deadlock and show a false positive:

 

 

To provide a reliable test context the non-deterministic statement has to be replaced by a deterministic one. Since Typemock Isolator can't mock types of the mscorlib the condition of the if statement has to be refactored. This step has beside the improvement of the testability another advantage: We can eliminate a code smell and improve the readability of the code:

 

       internal void Work()
        {
            //Do long running work

            //Notify service
            if (CurrentTicksAreEven())
                _service.ChangeObjectStateWithLockOrderAB();
            else
                _service.ChangeObjectStateWithLockOrderBA();
        }

        internal static bool CurrentTicksAreEven() {
                return DateTime.Now.Ticks %2 == 0;
        }

 

After this modification the non-deterministic part of the code is ready to be mocked. To test both cases when the current ticks are odd or even the test method is separated into two different test methods:

 


        [TestMethod, Isolated]
        public void CheckForDeadlockWhenTicksAreOdd()
        {
            //Provide deterministic test context
            Worker workerMock = Isolate.Fake.Instance<Worker>(Members.CallOriginal);
            Isolate.Swap.AllInstances<Worker>().With(workerMock);
            Isolate.WhenCalled(() => workerMock.CurrentTicksAreEven()).WillReturn(false);

            Service service = new Service();
            ThreadTest
                .AddThreadAction(service.Serve)
                .Start();
        }

        [TestMethod, Isolated]
        public void CheckForDeadlockWhenTicksAreEven()
        {
            //Provide deterministic test context
            Worker workerMock = Isolate.Fake.Instance<Worker>(Members.CallOriginal);
            Isolate.Swap.AllInstances<Worker>().With(workerMock);
            Isolate.WhenCalled(() => workerMock.CurrentTicksAreEven()).WillReturn(true);

            Service service = new Service();
            ThreadTest
                .AddThreadAction(service.Serve)
                .Start();
        }

 

The Worker object is replaced by a mock every time it is created. Since there are four scenarios also four Worker objects will be created. Every call to a mock is delegated to the original – except for the newly introduced CurrentTicksAreEven method. Dependent of the test method the call of  CurrentTicksAreEven will return true or false and hence is under control during the execution of the test methods.

 

Now both test methods can perform their deadlock analysis and always come to a correct result. The test method CheckForDeadlockWhenTicksAreOdd will always detect a deadlock and CheckForDeadlockWhenTicksAreEven will never be able to detect one:

 

 

With these reliable test context a developer can go on to find and fix the deadlock problem if the non-deterministic statement would determine that the number of current ticks is odd.

 

Typemock Racer is a powerful tool to detect deadlocks in multi-threaded programs. The implementation of required test methods is easy and leads to fast results. With the support of Typemock Isolator even complex scenarios can be tested with an acceptable effort. In my opinion the current release proves that such a tool provides a great help for developers and will surely be enriched with additional features in the next time. Unfortunately the current documentation is very spare, but I guess it's based on the fact that this is an early release.

 

You can download the free trials on the Typemock website.

 

Sie sind hier: