Wednesday, May 13, 2009

How to test your multi-threaded code (part 3 of 3)?

In the last post we learned how to find and fix a simple multi-thread problem. Now we will see a more complex scenario and see how CHESS wil find the solution.

To do that we will add a ne method to our Account type.

public static void Transfer(double amount, Account fromAccount, Account toAccount)
{
    lock (fromAccount)
    {
        lock(toAccount)
        {
            fromAccount.Withdraw(amount);
            toAccount.Deposit(amount);
        }
    }
}

Because we want to be sure that the transfer works we lock both the “from” and the “to” account.

Now we can easily wrtie this test to see that this is working fine in signle threaded scenario.

[TestMethod]
public void TransferTest()
{
    Account a1 = Account.OpenNew(10000);
    Account a2 = Account.OpenNew(10000);

    Account.Transfer(100, a1, a2);
    Account.Transfer(100, a2, a1);

    Assert.AreEqual(10000, a1.Balance);
    Assert.AreEqual(10000, a2.Balance);
}

As we did before we will convert this single-thread method to a multi-thread one.

[TestMethod]
[HostType("Chess")]
public void TransferMultiThreadTest()
{
    Account a1 = Account.OpenNew(10000);
    Account a2 = Account.OpenNew(10000);

    Thread thread = 
new Thread(o => Account.Transfer(100, ((Account[]) o)[0], ((Account[]) o)[1])); thread.Start(new[] {a1, a2}); Account.Transfer(100, a2, a1); thread.Join(); Assert.AreEqual(10000, a1.Balance); Assert.AreEqual(10000, a2.Balance); }

Now if we run this CHESS will detect a deadlock scenario. If you’ve done some SQL queries you know you should always try to lock all your resources always in the same order. But why doesn’t it working here. We have only one method that lock resources they should be locked in the same order every time, and that’s true. The deadlock occurs because in some cases a thread start to lock the fromAccount (or maybe the toAccount too) and get interrupt by another thread trying to do the same. Then both thread are waiting for each other to complete. Databases engine use timeouts to get out of these situation, but the lock keyword doesn’t support timeout, eventough it uses Monitor.Enter which support it. In another post I will show you how to build your own lock implementation that support timeout, but now we need to find a way to make our code thread safe.

We have to go back to our account class and do some changes.

public static object _locObject = new object();

public static void Transfer(double amount, Account fromAccount, Account toAccount)
{
    lock (_locObject)
    {
        fromAccount.Withdraw(amount);
        toAccount.Deposit(amount);
    }
}

We have to create a static lock object we can use to lock on. Because we are doing the lock in one signle operation our test will now run without any problem.

This is only a small overview of what CHESS can do.

No comments: