Committing a Transaction in Single-Phase and Multi-Phase
Each resource used in a transaction is managed by a resource manager (RM), whose actions are coordinated by a transaction manager (TM). The Enlisting Resources as Participants in a Transaction topic discusses how a resource (or multiple resources) can be enlisted in a transaction. This topic discusses how transaction commitment can be coordinated among enlisted resources.
At the end of the transaction, the application requests the transaction to be either committed or rolled back. The transaction manager must eliminate risks like some resource managers voting to commit while others voting to roll back the transaction.
If your transaction involves more than one resource, you must perform a two-phase commit (2PC). The two-phase commit protocol (the prepare phase and the commit phase) ensures that when the transaction ends, all changes to all resources are either totally committed or fully rolled back. All the participants are then informed of the final result. For a detailed discussion of the two-phase commit protocol, please consult the book "Transaction Processing : Concepts and Techniques (Morgan Kaufmann Series in Data Management Systems) ISBN:1558601902" by Jim Gray.
You can also optimize your transaction's performance by taking part in the Single Phase Commit protocol. For more information, see Optimization using Single Phase Commit and Promotable Single Phase Notification.
If you just want to be informed of a transaction's outcome, and do not want to participate in voting, you should register for the TransactionCompleted event.
Two-phase Commit (2PC)
In the first transaction phase, the transaction manager queries each resource to determine whether a transaction should be committed or rolled back. In the second transaction phase, the transaction manager notifies each resource of the outcome of its queries, allowing it to perform any necessary cleanup.
To participate in this kind of transaction, a resource manager must implement the IEnlistmentNotification interface, which provides methods that are called by the TM as notifications during a 2PC. The following sample shows an example of such implementation.
Public Class EnlistmentClass
Implements IEnlistmentNotification
Public Sub Prepare(ByVal myPreparingEnlistment As PreparingEnlistment) Implements System.Transactions.IEnlistmentNotification.Prepare
Console.WriteLine("Prepare notification received")
'Perform transactional work
'If work finished correctly, reply with prepared
myPreparingEnlistment.Prepared()
End Sub
Public Sub Commit(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.Commit
Console.WriteLine("Commit notification received")
'Do any work necessary when commit notification is received
'Declare done on the enlistment
myEnlistment.Done()
End Sub
Public Sub Rollback(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.Rollback
Console.WriteLine("Rollback notification received")
'Do any work necessary when rollback notification is received
'Declare done on the enlistment
myEnlistment.Done()
End Sub
Public Sub InDoubt(ByVal myEnlistment As Enlistment) Implements System.Transactions.IEnlistmentNotification.InDoubt
Console.WriteLine("In doubt notification received")
'Do any work necessary when indout notification is received
'Declare done on the enlistment
myEnlistment.Done()
End Sub
End Class
class myEnlistmentClass : IEnlistmentNotification
{
public void Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("Prepare notification received");
//Perform transactional work
//If work finished correctly, reply prepared
preparingEnlistment.Prepared();
// otherwise, do a ForceRollback
preparingEnlistment.ForceRollback();
}
public void Commit(Enlistment enlistment)
{
Console.WriteLine("Commit notification received");
//Do any work necessary when commit notification is received
//Declare done on the enlistment
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
Console.WriteLine("Rollback notification received");
//Do any work necessary when rollback notification is received
//Declare done on the enlistment
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
Console.WriteLine("In doubt notification received");
//Do any work necessary when indout notification is received
//Declare done on the enlistment
enlistment.Done();
}
}
Prepare phase (Phase 1)
Upon receiving a Commit request from the application, the transaction manager begins the Prepare phase of all the enlisted participants by calling the Prepare method on each enlisted resource, in order to obtain each resource's vote on the transaction.
Your resource manager that implements the IEnlistmentNotification interface should first implement the Prepare method as the following simple example shows.
public void Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("Prepare notification received");
//Perform work
Console.Write("reply with prepared? [Y|N] ");
c = Console.ReadKey();
Console.WriteLine();
//If work finished correctly, reply with prepared
if ((c.KeyChar == 'Y') || (c.KeyChar == 'y'))
{
preparingEnlistment.Prepared();
break;
}
// otherwise, do a ForceRollback
else if ((c.KeyChar == 'N') || (c.KeyChar == 'n'))
{
preparingEnlistment.ForceRollback();
break;
}
}
When the durable resource manager receives this call, it should log the transaction's recovery information (available by retrieving the RecoveryInformation property) and whatever information is necessary to complete the transaction on commit. This does not need to be performed within the Prepare method because the RM can do this on a worker thread.
When the RM has finished its prepare work, it should vote to commit or roll back by calling the Prepared or ForceRollback method. Notice that the PreparingEnlistment class inherits a Done method from the Enlistment class. If you call this method on the PreparingEnlistment callback during the Prepare phase, it informs the TM that it is a Read-Only enlistment (that is, resource managers that can read but cannot update transaction-protected data) and the RM receives no further notifications from the transaction manager as to the outcome of the transaction in phase 2.
The application is told of the successful commitment of the transaction after all the resource managers vote Prepared.
Commit phase (Phase 2)
In the second phase of the transaction, if the transaction manager receives successful prepares from all the resource managers (all the resource managers have invoked Prepared at the end of phase 1), it invokes the Commit method for each resource manager. The resource managers can then make the changes durable and complete the commit.
If any resource manager reported a failure to prepare in phase 1, the transaction manager invokes the Rollback method for each resource manager and indicates the failure of the commit to the application.
Thus, your resource manager should implement the following methods.
public void Commit (Enlistment enlistment)
{
// Do any work necessary when commit notification is received
// Declare done on the enlistment
enlistment.Done();
}
public void Rollback (Enlistment enlistment)
{
// Do any work necessary when rollback notification is received
// Declare done on the enlistment
enlistment.Done();
}
The RM should perform any work necessary to finish the transaction based on the notification type, and inform the TM that it has finished by calling Done method on the Enlistment parameter. This work can be done on a worker thread. Note that the phase 2 notifications can happen inline on the same thread that called the Prepared method in phase 1. As such, you should not do any work after the Prepared call (for example, releasing locks) that you would expect to have completed before receiving the phase 2 notifications.
Implementing InDoubt
Finally, you should implement the InDoubt method for the volatile resource manager. This method is called if the transaction manager loses contact with one or more participants, so their status is unknown. If this occurs, you should log this fact so that you can investigate later whether any of the transaction participants has been left in an inconsistent state.
public void InDoubt ()
{
// log this
enlistment.Done();
}
Single Phase Commit Optimization
The Single Phase Commit protocol is more efficient at run time because all updates are done without any explicit coordination. For more information on this protocol, see Optimization using Single Phase Commit and Promotable Single Phase Notification.
See Also
Concepts
Optimization using Single Phase Commit and Promotable Single Phase Notification
Enlisting Resources as Participants in a Transaction