Developing an XA Transaction Manager for MSDTC
Applies To: Windows 10, Windows 7, Windows 8, Windows 8.1, Windows Server 2008, Windows Server 2008 R2, Windows Server 2012, Windows Server 2012 R2, Windows Server Technical Preview, Windows Vista
This topic discusses how to develop an XA transaction manager (XA TM) that interfaces with MSDTC to send XA transactions to OleTx-compliant resource managers, such as Microsoft SQL server or MSMQ. In this scenario, a client application is the XA TM, while MSDTC is used as an XA-compliant resource manager. MSDTC then provides interfaces to import an XA transaction into MSDTC. Once imported, you can retrieve the ITransaction interface that can be used to send the transaction to the OleTx-based resource manager.
Here are the steps to get started:
Connecting with MSDTC
Retrieving an IXATransLookup2 Interface
Importing the Transaction into MSDTC
Retrieving the Interface to the Imported Transaction
Implementing Two-Phase Commit Protocol
Recovering In-Doubt Transactions
Suspending Work on the Transaction
Closing the Resource Manager
Connecting with MSDTC
To connect MSDTC with an XA TM, you first need to statically link to the xaswitch.lib library of the Microsoft SDK. The library exposes the msqlsrvxa1 structure, which is the MSDTC implementation of the xa_switch_t structure defined by the XA specification. The fields of msqlsrvxa1 contain function pointers to the XA functions in MSDTC.
You must add this code to any C or C++ source files that call XA functions in MSDTC:
extern "C"
{
extern struct xa_switch_t msqlsrvxa1;
}
The XA TM needs to initialize MSDTC before the XA TM calls other XA functions. The xa_open_entry function performs the initialization. In this example, xa_open_entry is called to initialize MSDTC as a resource manager:
LPSTR pxa_info;
int myRmId;
long flags = 0;
int xaerr = msqlsrvxa1.xa_open_entry(pxa_info, myRmId, flags);
See xa_open for information about xa_open_entry.
If the XA TM calls xa_open_entry multiple times, it must provide a unique value for the rmid
parameter, and a unique GUID for the RMRecoveryGuid name/value pair in the xa_info
parameter.
If the call succeeds, the XA TM is connected to MSDTC, and can use MSDTC as a resource manager for XA transactions.
Retrieving an IXATransLookup2 Interface
You can use the IXATransLookup2 interface to retrieve an ITransaction interface pointer that points to the XA transaction after it’s imported into MSDTC. To use IXATransLookup2, you must first retrieve a pointer to the interface from MSDTC by calling the DtcGetTransactionManagerEx function. Another option is to retrieve a different interface pointer with DtcGetTransactionManagerEx, and then use the QueryInterface
function to retrieve the pointer to IXATransLookup2.
In this example, DtcGetTransactionManagerEx is called to retrieve a pointer to an IXATransLookup2 interface:
HRESULT hr = S_OK;
IXATransLookup2* pXATxLookup2;
hr = DtcGetTransactionManagerExA( NULL, NULL, IID_IXATransLookup2, 0, NULL, (void**) &pXATxLookup2 );
The IXATransLookup2 interface pointer can be called multiple times to retrieve ITransaction interface pointers for multiple XIDs. You only need to obtain an IXATransLookup2 interface pointer once in the lifetime of an OleTx connection to MSDTC. However, if the connection to MSDTC goes down, you must retrieve the interface pointer again.
Importing the Transaction into MSDTC
After xa_open_entry returns successfully, you can import the transaction into MSDTC using the xa_start_entry function. If xa_start_entry returns successfully, MSDTC will contain an imported transaction that corresponds to the XA transaction.
In this example, an XA TM calls xa_start_entry with MSDTC identified as the resource manager. If this call is successful, MSDTC will import the transaction.
xaerr = msqlsrvxa1.xa_start_entry(&myXid, myRmId, startFlags);
See xa_start for information about xa_start_entry.
Retrieving the Interface to the Imported Transaction
After you retrieve the IXATransLookup2 interface pointer and import the transaction into MSDTC, you can retrieve a pointer to the ITransaction interface that manages the imported transaction. You can retrieve the ITransaction pointer by using the IXATransLookup2 pointer that you previously obtained.
In this example, an IXATransLookup2 pointer is used to retrieve an ITransaction pointer.
ITransaction* pTx;
hr = pXATxLookup2->Lookup( &myXid, &pTx );
After you retrieve the ITransaction pointer, you can use it to retrieve the export cookie or propagation token for the transaction that is used to send the imported transaction to an OleTx-compliant resource manager. The OleTx-compliant resource manager can then communicate with MSDTC through OleTx interfaces that enlist on the transaction.
Implementing Two-Phase Commit Protocol
When an XA TM calls commit operations, it needs to implement two-phase commit protocol to ensure that the resource managers either commit or rollback the transaction. You can call use xa_prepare_entry and xa_commit_entry to implement two-phase commit protocol.
Here’s how an XA TM handles two-phase commit protocol:
The XA TM starts the first phase by calling xa_prepare_entry. If multiple resource managers are involved in the transaction, the XA TM must call xa_prepare_call on each resource manager.
If the prepare operations complete successfully, the XA TM must write a commit record to its log to make a permanent record of the decision.
After the commit decision is logged, the XA TM must notify the resource managers that the transaction was committed by calling xa_commit_entry.
If the transaction needs to be aborted instead of committed, the XA TM must call xa_rollback_entry instead of xa_commit_entry. The xa_rollback_entry call can be made after the xa_prepare_entry call, or it can replace the xa_prepare_entry call.
In this example, an XA TM calls xa_prepare_entry:
xaerr = msqlsrvxa1.xa_prepare_entry(&myXid, myRmId, prepareFlags);
See xa_prepare for information about xa_prepare_entry.
In this example, an XA TM calls xa_commit_entry:
xaerr = msqlsrvxa1.xa_commit_entry(&myXid, myRmId, commitFlags);
See xa_commit for information about xa_commit_entry.
In this example, an XA TM calls xa_rollback_entry:
xaerr = msqlsrvxa1.xa_rollback_entry(&myXid, myRmId, rollbackFlags);
See xa_rollback for information about xa_rollback_entry.
If MSDTC is the only resource manager involved in the transaction, then the XA TM can delegate the commit decision to MSDTC by using single phase commit optimization. To use single phase commit optimization, the XA TM calls xa_commit_entry without first calling xa_prepare_entry. This notifies MSDTC that it is the only resource manager involved in the transaction, and that it makes the commit decision. Single Phase Commit optimization saves function calls and logging requirements for the transaction.
Recovering In-Doubt Transactions
An XA TM must start recovery processing for in-doubt transactions. A transaction is in-doubt if a prepare operation succeeds, but the resource manager wasn’t successfully notified of the commit decision. The cause could be a failure that occurred after the transaction was prepared on the resource manager.
Here’s how an XA TM handles recovery processing:
The XA TM starts recovery processing by calling xa_recover_entry. This is usually called after the xa_open_entry call.
The resource manager populates an array of XIDs that identify the in-doubt transactions.
The XA TM checks the log to determine the outcome of the in-doubt transactions, and then either commits or aborts each transaction.
If a transaction committed, the XA TM must keep the decision in the log until xa_commit_entry returns successfully.
If a transaction aborts, the XA TM no longer needs to keep the log record.
If an in-doubt transaction is not logged by the XA TM, the XA TM can abort the transaction by calling xa_rollback_entry.
In this example, an XA TM calls xa_recover_entry:
Note
In order to determine the number of in-doubt transactions to process, this call must be made in a loop.
XID* pXidArray = new XID[countOfXidsPerCall];
xaerr = msqlsrvxa1.xa_recover_entry(pXidArray, countOfXidsPerCall, myRmId, recoverFlags);
For information about xa_recover_entry, see xa_recover. For details on the recovery protocol, see the XA specification.
Suspending Work on the Transaction
When an XA TM finishes work on the transaction, it should call xa_end_entry to notify MSDTC that it has finished work on the transaction in the current thread.
In this example, an XA TM calls xa_end_entry:
xaerr = msqlsrvxa1.xa_end_entry(&myXid, myRmId, endFlags);
See xa_end for information about xa_end_entry.
Closing the Resource Manager
When an XA TM is done working with a resource manager, it should call xa_close_entry to clean up system resources.
In this example, an XA TM calls xa_close_entry:
int xaerr = msqlsrvxa1.xa_close_entry(pxa_info, myRmId, closeflags);
For information about xa_close_entry, see xa_close.
To make additional XA function calls after xa_close_entry is called, an XA TM needs to first repeat the xa_open_entry call.