Just as I promised... ;)
Today I’m blogging about two interesting things that people may have noticed in my last entry: the flag “enlistDuringPrepareRequired” and the significance of the byte named recoveryInformation passed to your code in IEnlistmentNotification.Prepare.
First up: What does the flag enlistDuringPrepareRequire (EDPR) do?
When a user wants to commit a transaction, it typically enters the two phase commit protocol (2PC). Normally, as soon as the transaction enters phase 1 and is in the process of committing, no more work should be done for the transaction and thus, no more enlistments can be made. However, let’s say you are creating a resource manager (RM) where enlistment is very expensive and thus, wants to delay work as far as possible. In other words, you don’t want to actually do work and enlist in the transaction until you are sure the transaction is about to commit. Perhaps sometimes, by delaying you can determine that you don’t actually need to do any work at all – the entire expensive enlistment operation is saved!
To facilitate this scenario, we have developed a Prepare phase that occurs after the transaction owner requests Commit but before the transaction actually enters Phase 1 of 2PC. Getting to this behavior is easy; simply do an enlistment in the transaction passing in true for the EDPR Boolean parameter. This tells System.Transactions that for this particular enlistment, you want the Prepare call to happen during this special time.
During this special time you know that the transaction is about to start 2PC and any work that you need to do must be done now. At this point, the RM can do work and enlist appropriately in the transaction; remember, normally once you’ve received Prepare, you cannot enlist in the transaction. Here is where if the RM doesn’t need to do any work, it saves the creation of a potentially expensive durable enlistment. Depending on the frequency of that scenario, this could be a huge performance gain. However, in the case that it does need to do work, this pattern incurs the relatively small cost of an extra EDPR=true enlistment.
Those who are astute will point out that during such an EDPR=true Prepare notification, the user code could enlist again with EDPR=true. In that particular scenario, there will be “waves” of pre-2PC Prepare notifications. What this means is that System.Transactions will batch the EDPR=true Prepare notifications up according to generation. All the EDPR=true enlistments that happened before the client called Commit are the first wave. Then, during this first wave, any additional EDPR=true enlistments are batched up into a second wave that happens only after all the notifications from the first wave are finished (enlistments signal that they are finished by calling back to System.Transactions using the IPreparingEnlistment provided). Within resource constraints, there is no limit to the number of EDPR=true Prepare waves you can have.
Next: What does this byte named recoveryInformation do?
If you decide that you need a durable enlistment (see my previous blog entry), then you need to account for failure conditions – specifically what happens if the RM goes down in the middle of a transaction that is committing. The pattern for correct RM behavior during 2PC transaction commit is well established and those steps (from the RM’s perspective) are:
- Wait to receive the IEnlistmentNotification.Prepare notification
- Make sure that it is ready to finalize the “work” done for the transaction
- Write a log record containing the recoveryInfo byte and some information pertaining to the work that was done so that it can be undone in case the transaction aborts
- Callback using IPreparingEnlistment.Prepared()
- Wait to receive the IEnlistmentNotification.Commit or Rollback notification
- Either finalize or revert the “work” done for the transaction
- Write a record that nulls the record created in step 3
- Callback using IEnlistment.EnlistmentDone()
The most classic scenario is what happens if the RM somehow fails between steps 4 and 5. In this scenario, the “work” is half finalized and is in limbo. To preserve data integrity, the RM needs to reconcile its log records when it recovers. To find out what the outcome of a transaction was, a recovering RM reads in recoveryInfo byte arrays from its log and calls:
IResourceManager myRM = new MyResourceManager();
for (int i = 0; i < numRecoveryInfoInLog; i++)
byte recoveryInformation = GrabRecoveryInfoFromLog(i);
IEnlistmentNotification en = new MyEnlistmentNotification();
TransactionManager.Reenlist(rm, recoveryInformation, en);
Then, the instances of MyEnlistmentNotification will receive the transaction outcomes for each recovery record. The actual contents of recoveryInformation are opaque and are an implementation detail of System.Transactions. However, you can safely assume that it contains at least the type of Transaction Manager you are connecting to, your RM’s identifier, and the transaction’s identifier.
To end this entry, there are a few different things I can cover next time, so I’ll take a vote. Leave me feedback or e-mail me (firstname.lastname@example.org) and let me know which of the following topics most interests you:
- How do I use System.Transactions with my existing components?
- How does serialization work (a.k.a. what happened to the complicated transaction marshaling I used to have to do)?
- What are all the details involved with creating a correct durable Resource Manager?
- What happens when MSDTC goes down?
- What is this LTM (Lightweight Transaction Manager) that I’ve heard so much about?
Don’t worry, all these topics and more will be covered eventually, but if you give me a priority, I’ll be able to get to the info you need faster! Also feel free to suggest any other topic you’d like to hear about J