2007-06-05

Writing a resource manager for System.Transactions

Currently I'm developing a O/R mapper that needs a Resource Manager to keep track of the inner states of the objects that it maps when a transaction starts. To do this I want to create a ResourceManager that can participate in a transaction and be commited and rolled back in line with the other managers in the transaction. The ISinglePhaseNotification interface seemed to be a great interface to use.

I wrote a simple class that implemented the ISinglePhaseNotification interface and tried it. At first it seemed to work like a charm, but then I got these strange behaviors.
I wrote a little test that enlisted the resource manager in a transaction, and when the transaction was completed, checked the Commited flag (that I set when the IEnlistmentNotification.Commit has been finished).

MyResourceManager rm = new MyResourceManager();
using (TransactionScope s = new TransactionScope()) {
rm.Enlist();
s.Complete();
}
Assert.AreEqual(true, rm.Committed);

The problem is that the test don't always succeed. This seemed like a school example for a resource manager
  1. create one
  2. enlist into the transaction
  3. mark the transaction as complete
  4. end the transaction
  5. validate that the resource manager has been committed.
The error was more reliable when the commit took longer to execute (sleep a few seconds). It seems like when the TransactionScope is disposed, the transaction threads the calls to the resource managers and don't wait for them to finish before continuing.

This behavior has to work since after a transaction ends, I need to update/change the data of the objects in the system and they need to be available immediately after the TransactionScope has ended.
I hope this only is configuration issue and that I have missed some central issue regarding Resource managers and Transactions. Please enlighten me...

I post the complete test code below for you to use to reproduce the test. You'll see that the Assert will never work since the Commit will not be set at once but only after a small timeout (2 sec).

If I run the test with the Assert commented i will receive the following output.

TransactionScope BEGIN
IEnlistmentNotification.Prepare
IEnlistmentNotification.Commit BEGIN
TransactionScope END
IEnlistmentNotification.Commit END


And this illustrates the problem. The transaction scope is ended before the commit has been completely done.
Has someone some ideas?

Regards
/Dan


using System;
using System.Threading;
using System.Transactions;
using NUnit.Framework;

namespace OneProcess {
[TestFixture]
public class Test2 {
[Test]
public void OnlyLocalTest() {

MyResourceManager rm = new MyResourceManager();
Console.WriteLine("TransactionScope BEGIN");
using (TransactionScope s = new TransactionScope()) {
rm.Enlist();
s.Complete();
}
Console.WriteLine("TransactionScope END");
Assert.AreEqual(true, rm.Committed);
Thread.Sleep(3000);

}
public class MyResourceManager
: ISinglePhaseNotification {
public bool Committed = false;
public void Enlist() {
if (null != Transaction.Current) {
Transaction.Current.EnlistDurable(
Guid.NewGuid(), this,
EnlistmentOptions.EnlistDuringPrepareRequired);
}
}

void ISinglePhaseNotification.SinglePhaseCommit(
SinglePhaseEnlistment singlePhaseEnlistment) {
Console.WriteLine(
"ISinglePhaseNotification.SinglePhaseCommit");
Committed = true;
singlePhaseEnlistment.Committed();
}


void IEnlistmentNotification.Commit(Enlistment enlistment) {
Console.WriteLine(
"IEnlistmentNotification.Commit BEGIN");
Thread.Sleep(2000);
Committed = true;
enlistment.Done();
Console.WriteLine(
"IEnlistmentNotification.Commit END");
}

void IEnlistmentNotification.InDoubt(Enlistment enlistment) {
Console.WriteLine("IEnlistmentNotification.InDoubt");
enlistment.Done();
}

void IEnlistmentNotification.Prepare(
PreparingEnlistment preparingEnlistment) {
Console.WriteLine("IEnlistmentNotification.Prepare");
preparingEnlistment.Prepared();
}
void IEnlistmentNotification.Rollback(Enlistment enlistment) {
Console.WriteLine("IEnlistmentNotification.Rollback");
Thread.Sleep(2000);
enlistment.Done();
}
}
}
}

1 comment:

  1. I wrote a question in the Programming Transactions forum at Microsoft.
    http://forums.microsoft.com/msdn/showpost.aspx?postid=1692039
    Follow that thread for more information about the issue.

    ReplyDelete