Propagating MSDTC transactions

MSDTC offers two types of propagation for transactions: Export/Import (the most known one) and Transmitter/Receiver. The semantic difference between the two is that the Export/Import is a “push” model (the transaction is “pushed” to the remote machine at the moment when Export is called), while Transmitter/Receiver is a “pull” model (the transaction is “pulled” from the remote machine, only and only when Unmarshal is called).


Let’s assume the following scenario: One distributed transaction is available in the client application and it needs to be “propagated” to a remote machine where the resource manager is located so that it can be used for enlisting that resource manager as part of the transaction.


For the Export/Import model, the steps to implement the scenario are:


1. On the remote machine, obtain the “whereabouts” of the transaction manager of that machine


            ITransactionImportWhereabouts* pITIW = NULL;

            hr = pTransactionDispenser->QueryInterface(IID_ITransactionImportWhereabouts, (void**)&pITIW);

            ULONG cbWhereabouts = 0;

            hr = pITIW->GetWhereaboutsSize(&cbWhereabouts);

            byte* pWhereabouts = new byte[cbWhereabouts];

            ULONG cbUsed = 0;

            hr = pITIW->GetWhereabouts(cbWhereabouts, pWhereabouts, &cbUsed);


2. Send the blob containing the whereabouts (pWhereabouts) to the client machine using any preferred method

3. On the client machine, use the whereabouts to “export” the transaction to the remote machine and generate a transaction “cookie


            ITransactionExportFactory* pTxExportFactory = NULL;

            hr = pTransactionDispenser->QueryInterface(IID_ITransactionExportFactory, (void**)&pTxExportFactory);

            ITransactionExport* pTxExport = NULL;

            hr = pTxExportFactory->Create(cbUsed, pWhereabouts, &pTxExport);

            ULONG cbTxCookieSize = 0;

            hr = pTxExport->Export(pTransaction, &cbTxCookieSize); // when this call is made, the transaction is “pushed” to the transaction manager of the remote machine

            byte* pTxCookie = new byte[cbTxCookieSize];

            ULONG cbUsedCookie = 0;

            hr = pTxExport->GetTransactionCookie(pTransaction, cbTxCookieSize, pTxCookie, &cbUsedCookie);


4. Send the blob containing the transaction “cookie” (pTxCookie) to the remote machine using any preferred method

5. On the remote machine, use the transaction “cookie” to obtain a clone of the client transaction


            ITransactionImport* pTxImport = NULL;

            hr = pTransactionDispenser->QueryInterface(IID_ITransactionImport, (void**)&pTxImport);

            ITransaction* pTx2 = NULL;

            IID iid = IID_ITransaction;

            hr = pTxImport->Import(cbUsedCookie, pTxCookie, &iid, (void**)&pTx2);


6. On the remote machine, use the clone transaction to enlist the resource manager using IResourceManager::Enlist or IResourceManager2::Enlist2



For the Transmitter/Receiver model, the steps are:


1. On the client machine, using the transaction generate a corresponding “propagation token


            ITransactionTransmitterFactory* pTxTransmitterFactory = NULL;

            hr = pTransactionDispenser->QueryInterface(IID_ITransactionTransmitterFactory, (void**)&pTxTransmitterFactory);

            ITransactionTransmitter* pTxTransmitter = NULL;

            hr = pTxTransmitterFactory->Create(&pTxTransmitter);

            hr = pTxTransmitter->Set(pTransaction);

            ULONG tokenSize = 0;

            hr = pTxTransmitter->GetPropagationTokenSize(&tokenSize);

            byte* token = new byte[tokenSize];

            ULONG tokenSizeUsed = 0;

            hr = pTxTransmitter->MarshalPropagationToken(tokenSize, token, &tokenSizeUsed);

            hr = pTxTransmitter->Reset();


2. Send the blob containing the “propagation token” of the transaction (token) to the remote machine using any preferred method

3. On the remote machine, use the “propagation token” to obtain a clone of the client transaction


            ITransactionReceiverFactory* pTxReceiverFactory = NULL;

            hr = pTransactionDispenser->QueryInterface(IID_ITransactionReceiverFactory, (void**)&pTxReceiverFactory);

            ITransactionReceiver* pTxReceiver = NULL;

            hr = pTxReceiverFactory->Create(&pTxReceiver);

            ITransaction* pTx2 = NULL;

            hr = pTxReceiver->UnmarshalPropagationToken(tokenSizeUsed, token, &pTx2); // only at this call, the two transaction managers of the two machines start communicating, and the transaction is “pulled” from the client machine to the server machine


4. On the remote machine, use the clone transaction to enlist the resource manager using IResourceManager::Enlist or IResourceManager2::Enlist2



The Transmitter/Receiver model provides a few advantages over the Export/Import model: it is simpler to implement and because it is a “lazy” model, it provides better performance in the cases where the transaction token is sent to a remote machine but the remote machine might or might not use it to obtain the transaction. Another advantage is that the “propagation token” can be sent to any remote machine, while the “cookie” can be used only on the machine that provided the whereabouts.

And yes, the Export/Import requires an extra step to get the whereabouts from the remote machine and send it to the client machine, but this step can be optimized to only one call, because the whereabouts can be cached on the client machine and reused as many times as possible. In fact, this is where the small disadvantage of the Transmitter/Receiver model is: the whereabouts will be carried in all the “propagation tokens”, making them bigger than the corresponding “transaction cookies” from Export/Import.


As in most of the enterprise scenarios, there isn’t a recommended or ‘best for all’ propagation. You have to always analyze the trade-offs, do some testing, and choose what’s best for your specific scenario.


Note: I eliminated the error handling (verification of the returned hr etc) in the code for clarity.

Comments (5)

  1. FabrizioC says:

    Hi Florin my name is FabrizioC,I’ve read your message, in my application I use Export/Import method calling the same APIs you called in your code from point 1 to point 5 only in point 6 I use a different solution, in fact I call JoinTransaction of ITransactionJoin interface.Could you explain the differences between these two steps?

    Thank you in advance


  2. System.Transactions transactions are bounded to the appdomain. Which means that if you make in-appdomain