Developing with Wait Chain Traversal

This article discusses building applications that use the new wait chain traversal (WCT) APIs in Windows ServerĀ® 2008. A conceptual overview of this feature can be found in the companion topic Understanding Wait Chain Traversal.

Debugging with WTC

Wait chain traversal (WCT) is a mechanism for debugging blocked threads and processes and detecting deadlocks. Using WCT, debugging software can analyze executing processes and report on the state of threads, including information such as what a blocked thread is waiting for and whether a deadlock condition exists. WCT allows debugging software to detect problems both within and across process boundaries.

To analyze an application, debugging software gets the currently executing process and/or thread identifiers, and then uses WCT to obtain the wait chain that shows the state of the threads. To use WCT, a debugging application must perform the following steps:

  1. Create a WTC session.

  2. Get the processes and threads to analyze.

  3. Get the wait chain for each thread.

  4. Close the WTC Session.

This article describes developing with WCT synchronously in unmanaged applications. For information on using WCT asynchronously, see Wait Chain Traversal. For a detailed discussion on using WTC in managed applications, see BugSlayer: Wait Chain Traversal.

Create a WTC Session

To create a WCT session, applications call the OpenThreadWaitChainSession function. This function takes a parameter that allows an application to create a synchronous or asynchronous session. When opening an asynchronous session, the caller specifies the WaitChainCallback callback function that WCT will invoke to return information to the caller. The OpenThreadWaitChainSession function returns a handle to the session. The following code example demonstrates opening a synchronous wait chain session.

HWCT wctHandle = NULL;
wctHandle = OpenThreadWaitChainSession(0, NULL);
if (NULL == wctHandle)
{
    wprintf(L"Error opening wait chain session: %d\n", GetLastError());
    return 1;
}

Get the Processes and Threads to Analyze

Using WCT, an application can analyze its own threads or threads in other processes. To analyze threads in processes that are not owned by the current user requires the SE_DEBUG_NAME privilege. For an example that demonstrates enabling this privilege, see Using WCT.

The Tools Help Library functions provide a simple way to get the IDs of processes and threads executing on a system. To iterate over the threads on a system, an application first takes a snapshot of the system by calling the CreateToolhelp32Snapshot function. Using the snapshot, a debugging application can enumerate threads by calling the Thread32First and Thread32Next functions. The following code example demonstrates iterating over the threads in a snapshot.

HANDLE hThreadSnap;
THREADENTRY32 te32;

// Create a snapshot of the system.
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);
if (NULL == hThreadSnap)
{
    wprintf(L"CreateToolhelp32Snapshot error: %d\n", GetLastError());
    return FALSE;
}
te32.dwSize = sizeof(te32);

// Retrieve information about the first thread.
if(!Thread32First(hThreadSnap, &te32)) 
{
    wprintf(L"Thread32First error: %d\n", GetLastError());
    CloseHandle(hThreadSnap);
    return(FALSE);
}

// Process the first thread and then get the rest of the threads.
do 
{ 
    // Do thread processing here.
} while(Thread32Next(hThreadSnap, &te32)); 

Get the Wait Chain for Each Thread

The wait chain for a thread is obtained by calling the GetThreadWaitChain function. This function takes a handle to the WTC session, callback data if the session is asynchronous, flags to customize the data returned, and the thread's ID. The wait chain is returned in an array of WAITCHAIN_NODE_INFO structures. This structure returns different information depending on whether the node represents a thread or a synchronization object. The GetThreadWaitChain function also returns a Boolean value via the IsCycle parameter indicating a deadlocked state.

The following code example demonstrates getting the wait chain for a thread and processing the nodes in the chain.

THREADENTRY32 te32; 
DWORD nodeCount;
WAITCHAIN_NODE_INFO nodes[16];
BOOL bCycle;
int i;

do
{
    nodeCount = 16;

    // Get the wait chain for the thread.
    if(FALSE == GetThreadWaitChain(
        wtc, 
        (ULONG_PTR)0, 
        0,
        te32.th32ThreadID,
        &nodeCount, 
        nodes, 
        &bCycle))
    {
        wprintf(L"Error getting wait chain: %d.\n", GetLastError());
        CloseHandle(hThreadSnap);
        return FALSE;
    }

    for (i = 0; i< (int) nodeCount;i++)
    {
        switch (nodes[i].ObjectType)
        {
            // Process threads and synchronization objects.
            case WctThreadType:
               // Process thread nodes.
               break;
            default: 
               // Process synchronization object nodes.
               break;
        }
    }
} while(Thread32Next(hThreadSnap, &te32));

Close the WTC Session

To close an open WCT session, call the CloseThreadWaitChainSession function. For asynchronous sessions, this call cancels any pending callbacks.

Wait Chain Traversal Example

The code example in this section implements a function that takes a handle to an open WCT session, and displays the wait chain for the current process's threads. The following wait chain output was generated by an application snapshot containing two deadlocked threads, where each thread owns a mutual-exclusion object (mutex) and is blocked waiting to acquire the other thread's mutex.

Wait chains for process: 2648 main thread = 684

thread 684 [running]

Deadlock thread 3652 [blocked] | Mutex | thread 3628 [blocked] | Mutex | thread 3652 [blocked]

Deadlock thread 3628 [blocked] | Mutex | thread 3652 [blocked] | Mutex | thread 3628 [blocked]

The following output was generated by an application snapshot containing two threads that are blocked waiting to acquire a mutex owned by the application's main thread, and a third thread that is blocked waiting for a thread that is waiting to acquire a mutex.

Wait chains for process: 1984 main thread = 348

thread 348 [running]

thread 1732 [blocked] | Mutex | thread 348 [running]

thread 3132 [blocked] | Mutex | thread 348 [running]

thread 2696 [blocked] | ThreadWait | thread 3132 [blocked] | Mutex | thread 348 [running]

The following code example displays wait chains for the threads in the current process.

typedef struct _STR_ARRAY
{
    WCHAR Desc[32];
} STR_ARRAY;

// Names for some of the different synchronization types.
STR_ARRAY STR_OBJECT_TYPE[] =
{
    {L"CriticalSection"},
    {L"SendMessage"},
    {L"Mutex"},
    {L"Alpc"},
    {L"Com"},
    {L"ThreadWait"},
    {L"ProcWait"},
    {L"Thread"},
    {L"ComActivation"},
    {L"Unknown"},
    {L"Max"}
};

BOOL DisplayMyProcessWaitChain(HWCT wtc)
{
    HANDLE hThreadSnap;
    THREADENTRY32 te32; 
    DWORD myPID, nodeCount;
    WAITCHAIN_NODE_INFO nodes[16];
    BOOL bCycle;
    int i;

    // Create a snapshot of the system.
    hThreadSnap = CreateToolhelp32Snapshot(
        TH32CS_SNAPTHREAD,
        0);
    if (NULL == hThreadSnap)
    {
        wprintf(L"CreateToolhelp32Snapshot error: %d\n",
            GetLastError());
        return FALSE;
    }
    te32.dwSize = sizeof(te32);

    // Retrieve information about the first thread.
    if(!Thread32First(hThreadSnap, &te32)) 
    {
        wprintf(L"Thread32First error: %d\n", GetLastError());
        CloseHandle(hThreadSnap);
        return(FALSE);
    }
    myPID = GetCurrentProcessId();
    wprintf(L"\nWait chains for process: %d main thread = %d\n",
        myPID,GetCurrentThreadId());
    do
    {
        nodeCount = 16;

        // Just get the threads for this process.
        if(te32.th32OwnerProcessID != myPID)
        {
            continue;
        }

        // Get the wait chain for the thread.
        if(FALSE == GetThreadWaitChain(
            wtc, 
            (ULONG_PTR)0, 
            0,
            te32.th32ThreadID,
            &nodeCount, 
            nodes, 
            &bCycle))
        {
             wprintf(L"Error getting wait chain: %d.\n", GetLastError());
             CloseHandle(hThreadSnap);
             return FALSE;
        }

        // Is this wait chain deadlocked?
        if (bCycle) wprintf(L"Deadlock ");

        // Print nodes in the chain.
        for (i = 0; I < (int) nodeCount;i++)
        {
             if (i > 0)
                 wprintf(L" | ");
             switch (nodes[i].ObjectType)
             {
                // Display the thread ID and whether it is running.
                case WctThreadType:
                    wprintf(L"thread %d [%s]",
                      nodes[i].ThreadObject.ThreadId,
                      ((nodes[i].ObjectStatus == WctStatusBlocked) ? 
                      L"blocked" : L"running"));
                    break;
                default: // Got a synchronization object.
                    wprintf(L"%s",
                      STR_OBJECT_TYPE[nodes[i].ObjectType-1].Desc);
                    break;
             }
        }
        wprintf(L"\n\n");
    } while(Thread32Next(hThreadSnap, &te32)); 

    CloseHandle(hThreadSnap);
    wprintf(L"\n");
    return TRUE;
}

See Also

Concepts

Understanding Wait Chain Traversal

Wait Chain Traversal