Listing Running Transactions In A Resource Manager

 

The more I work with TxF, the more I realize that our (Microsoft's) coverage of certain KTM topics is sorely lacking. One of those areas is around documentation and samples of managing resource managers (important if you are wanting to investigate programatically the running status of a resource manager).

Sometimes, being an evangelist can be quite fun (or frustrating) because the documentation leaves something to be desired and you can't run to the internet because the only results you get back or to the documentation on MSDN. And of course, since you're on the cutting searching for the control codes online provides you with two results, both pointing back to MSDN. It becomes doubly "interesting" if you are a managed developer because a large number of management-oriented information for KTM is all done through DeviceIoControl (an API that is NOT fun to try to P/Invoke into, especially using certain control codes).

Anyways, that's all beside the point... moving right along.

If you are writing an application to monitor TxF (or even building it into your own application), there are some core "things" that you need to do. One of the first ones (and the one I will cover in this post) is to get a list of transactions in a given resource manager. This is done using the FSCTL_TXFS_LIST_TRANSACTIONS control code.

What structure is DeviceIoControl passing back to us in this instance? A TXFS_LIST_TRANSACTIONS structure. Looking at this structure, we see two fields: NumberOfTransactions and BufferSizeRequired.
That's nice and all, but where is the information about those transactions? The information is stored in a TXFS_LIST_TRANSACTIONS_ENTRY structure.

The trick is that the TXFS_LIST_TRANSACTIONS_ENTRY structure is mentioned nowhere in the documentation for FSCTL_TXFS_LIST_TRANSACTIONS. So how do you get to it? An array of TXFS_LIST_TRANSACTIONS_ENTRY structures are stored in memory after your TXFS_LIST_TRANSACTIONS structure. So assuming you have two running transactions in the given resource manager, you would pass in a pointer to the TXFS_LIST_TRANSACTIONS structure, and here is what the memory would actually look like coming back from DeviceIoControl:

The impact of this is that you need to allocate enough memory for not only your TXFS_LIST_TRANSACTIONS structure, but the array of TXFS_LIST_TRANSACTIONS_ENTRY structures as well. You might notice a little bit of a "chicken and egg" problem here. If I'm using FSCTL_TXFS_LIST_TRANSACTIONS to retrieve the transactions, how could I know how many entry structures to allocate when I don't know how many transactions there are yet?

Well, assuming there are one or more running transactions in the resource manager, you have to call DeviceIoControl with FSCTL_TXFS_LIST_TRANSACTIONS at least twice: once to find out how big of a buffer you need to allocate (read: to calculate how many ENTRY structures to accomodate for), and then another time after allocating the extra memory to have it fill the actual ENTRY structures.

Why do I say "at least" twice? Because, between the time we make our first call and the time we make our second call, more transactions could have appeared and we will have to allocate even more memory. A little bit of a "cat and mouse game," I must say.

If you are a managed developer, this may sound a bit scary. Don't be scared though, it's not too bad (and I know I at least have fun writing this kind of lower-level code :P).

So let's dive right in and look at the code to make this work!

In pseudo-code, this is what we have to do:

------------------------
while (true)
{
    // Deallocate memory from last loop through this

    // Allocate memory for transaction list buffer

    // Call DeviceIoControl with FSCTL_TXFS_LIST_TRANSACTIONS

    // If DeviceIoControl Succeeded

    // Get list of transaction entries after TXFS_LIST_TRANSACTIONS structure
}

// Deallocate any remaining memory
------------------------

Not that bad really. So here it is implemented in native code:

1 #include

2 #include

3 #include

4 #include "Winioctl.h"

5

6 int _tmain(int argc, LPTSTR argv)

7 {

8     DWORD lastError, bytesReturned;

9     HANDLE rmDirectory;

10     TXFS_LIST_TRANSACTIONS *txList = NULL;

11

12     // Get handle to directory where resource manager resides

13     rmDirectory = CreateFile(TEXT("C:\\"),

14         GENERIC_READ,

15         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,

16         NULL,

17         OPEN_EXISTING,

18         FILE_FLAG_BACKUP_SEMANTICS,

19         NULL);

20

21     if (rmDirectory != INVALID_HANDLE_VALUE)

22     {

23         // Get a list of transactions within the given

24         // resource manager

25         DWORD neededBufferSize = sizeof(TXFS_LIST_TRANSACTIONS);

26         while (true)

27         {

28             // Deallocate previously allocated memory if we are back

29             // here after getting back ERROR_MORE_DATA

30             if (NULL != txList)

31             {

32                 delete[] (char*)txList;

33                 txList = NULL;

34             }

35

36             // Get the list of running transactions in the RM

37             txList = (TXFS_LIST_TRANSACTIONS*)new char[neededBufferSize];

38             if (!DeviceIoControl(rmDirectory,

39                 FSCTL_TXFS_LIST_TRANSACTIONS,

40                 NULL,

41                 0,

42                 txList,

43                 neededBufferSize,

44                 &bytesReturned,

45                 NULL))

46             {

47                 // How did we fail?

48                 lastError = GetLastError();

49                 if (lastError == ERROR_MORE_DATA)

50                 {

51                     // We need to enlarge the buffer and try again

52                     neededBufferSize = txList->BufferSizeRequired;

53                     continue;

54                 }

55                 else

56                 {

57                     _tprintf(TEXT("Unhandled exception: %u\n"), lastError);

58                     break;

59                 }

60             }

61             else

62             {

63                 _tprintf(TEXT("Transaction Count: %u\n\n"), txList->NumberOfTransactions);

64

65                 // List Tx details

66                 TXFS_LIST_TRANSACTIONS_ENTRY *txEntry;

67                 txEntry = (TXFS_LIST_TRANSACTIONS_ENTRY *)((char *)txList

                        + sizeof(TXFS_LIST_TRANSACTIONS));

68                 for (DWORD i = 0; i < txList->NumberOfTransactions; i++)

69                 {

70                     txEntry++;

71

72                     // Get GUID string

73                     WCHAR szGuid[40];

74                     StringFromGUID2(txEntry->TransactionId, (WCHAR *)&szGuid, 40);

75

76                     // Print Tx info

77                     _tprintf(TEXT("TX %s: %u\n"),

78                         szGuid,

79                         txEntry->TransactionState);

80                 }

81

82                 break;

83             }

84         }

85

86         // Any memory left to deallocate?

87         if (NULL != txList)

88         {

89             delete[] (char*)txList;

90             txList = NULL;

91         }

92     }

93     else

94     {

95         _tprintf(TEXT("Unable to get valid handle to Resource Manager\n"));

96         return 1;

97     }

98

99     _tprintf(TEXT("\nPress any key to exit...\n"));

100     getchar();

101

102     return 0;

103 }

And that's how you do it. It's not the most fun getting in to DeviceIoControl (unless you're a geek like me and enjoying this kind of stuff), but hopefully with this post out there to help it will be easier to find out how to do this since there is a lack of documentation around this area.

In closing, there is some work that needs to be done around making management of kernel-level transactions a lot easier than it is now. In some ways, it is pretty clear that this is still a "1.0" technology. So while you could use the KTM directly for your transaction coordination, I would highly recommend sticking with using DTC for your transaction coordination as it is much more mature from a management perspective (and there are quite a few enhancements with DTC in Vista and Windows Server 2008).