Need useful documentation/sample for using RecoveryInformation() in a .NET durable RM

It's really dissappointing to see how poor the .NET documentation is for using IEnlistmentNotification to create a durable RM that also uses RecoveryInformation().

My goal is incredibly simple: recreate the CompensatingResourceManager MSDN sample (http://msdn2.microsoft.com/en-us/library/8xkdw05k.aspx) using .NET 2.0.

1. How can I set RecoveryInformation() in advance of my Prepare() being called?

2. If I am supposed to set RecoveryInformation in Prepare(), what sort of design pattern should I use to pass my worker component parameters to Prepare()? ...that is, how do I perform the equivalent of my Clerk.WriteLog() calls?

Michael.

[851 byte] By [MichaelHerman-Parallelspace-] at [2008-2-19]
# 1

The recovery process for System.Transactions is explained at http://msdn2.microsoft.com/en-us/library/ms229982.aspx

Let me know if this answers your questions.

FlorinLazar-MSFT at 2007-8-30 > top of Msdn Tech,Software Development for Windows Vista,Transactions Programming...
# 2

Here is a quick&dirty sample I just wrote to show how to do recovery:

namespace SysTxRecoverySample

{

class Program

{

static void Main(string[] args)

{

SampleRM rm = new SampleRM();

rm.Init();

using( TransactionScope ts = new TransactionScope() )

{

rm.OpenConnection();

rm.DoWork();

rm.CloseConnection();

ts.Complete();

}

}

}

class SampleRM : IEnlistmentNotification

{

public SampleRM()

{

}

public void Init()

{

// Do recovery if necessary

int nbOfBytes = 0;

byte[] recoveryInfo = null;

lock (recoveryLogFileLock)

{

FileStream recoveryLog = new FileStream(recoveryLogName, FileMode.OpenOrCreate, FileAccess.Read);

if (recoveryLog.Length != 0)

{

BinaryReader br = new BinaryReader(recoveryLog);

br.BaseStream.Position = 0;

nbOfBytes = br.ReadInt32();

if (nbOfBytes > 0)

{

// we have to recover a transaction

recoveryInfo = br.ReadBytes(nbOfBytes);

// we only support one transaction, so we are done

}

br.Close();

}

recoveryLog.Close();

}

if ( nbOfBytes > 0)

{

TransactionManager.Reenlist(rmGuid, recoveryInfo, this);

// if we supported more than one transaction we would have called Reenlist

// for each recovery info found in the recovery file that we saved during Prepare

}

// inform the TM that we don't have any more indoubt transactions

TransactionManager.RecoveryComplete(rmGuid);

}

public void OpenConnection()

{

if (null != Transaction.Current)

{

Transaction.Current.EnlistDurable(rmGuid, this, EnlistmentOptions.None);

}

}

public void CloseConnection()

{ }

public void DoWork()

{ }

#region IEnlistmentNotification Members

void IEnlistmentNotification.Commit(Enlistment enlistment)

{

// move data from durable temp to real data

// your code here

// nothing to recover, so we should remove the recovery info

lock (recoveryLogFileLock)

{

FileStream recoveryLog = new FileStream(recoveryLogName, FileMode.OpenOrCreate, FileAccess.Write);

BinaryWriter bw = new BinaryWriter(recoveryLog);

bw.BaseStream.Position = 0;

bw.Write((int)0);

bw.Close();

recoveryLog.Close();

}

enlistment.Done();

}

void IEnlistmentNotification.InDoubt(Enlistment enlistment)

{

throw new Exception("The method or operation is not implemented.");

}

void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)

{

// move data from volatile temp to durable temp

// your code here

// save the recovery info

lock (recoveryLogFileLock)

{

FileStream recoveryLog = new FileStream(recoveryLogName, FileMode.OpenOrCreate, FileAccess.Write);

BinaryWriter bw = new BinaryWriter(recoveryLog);

bw.BaseStream.Position = 0;

bw.Write(preparingEnlistment.RecoveryInformation().Length);

bw.Write(preparingEnlistment.RecoveryInformation());

bw.Close();

recoveryLog.Close();

}

preparingEnlistment.Prepared();

}

void IEnlistmentNotification.Rollback(Enlistment enlistment)

{

// delete data from durable temp

// your code here

// nothing to recover, so we should remove the recovery info

lock (recoveryLogFileLock)

{

FileStream recoveryLog = new FileStream(recoveryLogName, FileMode.OpenOrCreate, FileAccess.Write);

BinaryWriter bw = new BinaryWriter(recoveryLog);

bw.BaseStream.Position = 0;

bw.Write((int)0);

bw.Close();

recoveryLog.Close();

}

enlistment.Done();

}

#endregion

private Guid rmGuid = new Guid("{48AE8985-368A-4e9e-9858-D25AD7120D8D}");

private string recoveryLogName = "SampleRecoveryLog.log";

private object recoveryLogFileLock = new object();

}

}

FlorinLazar-MSFT at 2007-8-30 > top of Msdn Tech,Software Development for Windows Vista,Transactions Programming...
# 3

I appreciate the work that went into the sample Florin.

Questions:

1. If DoWork() has parameters, string Arg1 and int Arg2, how/where would I save these parameters in RecoveryInfo (so that they are available in my Commit() and Rollback() methods if called?

2. What if I have more than one instance of my SampleRM? The (understandably) simple implmentation of the sample recoveryLog isn't sufficient.

My main comment/feedback is the design and functionality delivered in .NET 2.0 System.Transactions is less than what was previously available in .NET 1.1 EnterpriseServices - specifically related to the dropped support for the Clerk/persistent log functionality.

Michael.

# 4

You control what goes into the recovery file. Resource managers store per each transaction data about lock information, temporary data that is in prepared state (i.e. not committed or aborted) etc.

The preparingEnlistment.RecoveryInformation() content is useful when the resource manager recovers and it needs to find out what happened with the transaction. For each transaction (identified by its recovery info blob), the RM calls Reenlist and the transaction manager will (re)deliver the outcome: Commit or Abort. Based on the outcome, the RM will do the corresponding action with the data from the recovery file associated with that specific transaction.

My example only shows how to use the recoveryinfo of the transaction to do recovery. A real RM usually has tables of data, where each row is associated with a transaction. In order to handle multiple transactions in the same time, the RM needs to have multiple IEnlistmentNotification objects (instead of one as it is in my example). It also handles locks and temporary data per each enlistment/transaction. The recoveryinfo is a mechanism that allows recovery when failures/crashes appear before phase 2 of the transaction (when Commit/Abort happens) is complete. What is actually "recovered" is specific to each RM.

I don't understand your second question - can you clarify please?

Thanks for your feedback. It has been noted. We didn't drop support for a CRM; the release time didn't allow us to ship a System.Transactions based CRM and, in the same time, a CRM was already available in System.EntepriseServices.

HTH - Cheers!

FlorinLazar-MSFT at 2007-8-30 > top of Msdn Tech,Software Development for Windows Vista,Transactions Programming...

Software Development for Windows Vista

Site Classified