N.Cherney
N.Cherney

Reputation: 59

TerminateThread() with CloseHandle() on thread which uses only stack plain variables (without alloc) leaks memory?

I've created simple test which creates two thread; first one (WorkerThreadFun) executes infinite loop and second one (WorkerGuardThreadFun) terminates it with small timeout.

Thread to be terminated does not seem to do expicit allocation (at least inside WorkerThreadFun) and uses only stack variables of plain C type so I hope the stack will be deallocated by TerminateThread() with CloseHandle().

By some reason this test leaks memory on my Win7.

Where is the unbalanced heap allocation?

#include <windows.h>
#include <synchapi.h>
#include <assert.h>
#include <stdio.h>

#define STK_SIZE 4097

typedef enum
{
    GRD_IDLE,
    GRD_READY,
    GRD_TASKSTARTING,
    GRD_TASKWAITING
} GuardThreadState;

typedef struct
{
    HANDLE mHworkerThread;
    HANDLE mHworkerGroupThread;

    volatile int mIsWorkerStarted;

    GuardThreadState mGuardThreadState;

    CRITICAL_SECTION mLock;
    CONDITION_VARIABLE mThreadReadyCond;
    CONDITION_VARIABLE mStartTaskCond;
    CONDITION_VARIABLE mTaskFinishedCond;
} WorkerThreadHolder;

/*
typedef VOID(WINAPI *PRtlFreeUserThreadStack)(HANDLE hProcess, HANDLE hThread);
static PRtlFreeUserThreadStack RtlFreeUserThreadStack = NULL;
*/

DWORD WINAPI WorkerThreadFun(_In_ LPVOID p);
DWORD WINAPI WorkerGuardThreadFun(_In_ LPVOID p);

void Start(WorkerThreadHolder *workerThreadHolderPtr);
void ExecuteTask(WorkerThreadHolder *workerThreadHolderPtr);

/*----------------------------------------------------------------------------*/

DWORD WINAPI WorkerThreadFun(_In_ LPVOID p)
{
    /* use stack variables only in this thread in hope the stack will be deallocated by TerminateThread() */
    WorkerThreadHolder *workerThreadHolderPtr = (WorkerThreadHolder *)p;
    volatile int i;

    workerThreadHolderPtr->mIsWorkerStarted = 1;
    /*WakeAllConditionVariable(&workerThreadHolderPtr->mThreadReadyCond);*/

    /* do nothing for infinite long time */
    for(i = 0;; ++i)
        i = i;

    /*WakeAllConditionVariable(&workerThreadHolderPtr->mTaskFinishedCond);*/

    return 0;
}

DWORD WINAPI WorkerGuardThreadFun(_In_ LPVOID p)
{
    const DWORD taskExecutionTimeoutInMillisec = 1;
    WorkerThreadHolder *workerThreadHolderPtr = (WorkerThreadHolder *)p;

    EnterCriticalSection(&workerThreadHolderPtr->mLock);

    workerThreadHolderPtr->mGuardThreadState = GRD_READY;

    WakeAllConditionVariable(&workerThreadHolderPtr->mThreadReadyCond);

    for (;;)
    {
        for (;;)
        {
            SleepConditionVariableCS(
                &workerThreadHolderPtr->mStartTaskCond,
                &workerThreadHolderPtr->mLock,
                INFINITE);

            if (workerThreadHolderPtr->mGuardThreadState == GRD_TASKSTARTING)
                break;
        }

        workerThreadHolderPtr->mGuardThreadState = GRD_TASKWAITING;

        {
            BOOL isTaskFinishedOk = FALSE;

            for (;;)
            {
                isTaskFinishedOk =
                    SleepConditionVariableCS(
                        &workerThreadHolderPtr->mTaskFinishedCond,
                        &workerThreadHolderPtr->mLock,
                        taskExecutionTimeoutInMillisec);

                if (!isTaskFinishedOk)
                    break;
            }

            if (isTaskFinishedOk)
            {
                /* never happens in this test */
            }
            else
            {
                BOOL isClosed;
                TerminateThread(workerThreadHolderPtr->mHworkerThread, 0);

                /*if (RtlFreeUserThreadStack != NULL)
                    RtlFreeUserThreadStack(GetCurrentProcess(), workerThreadHolderPtr->mHworkerThread);*/

                isClosed = CloseHandle(workerThreadHolderPtr->mHworkerThread);

                workerThreadHolderPtr->mIsWorkerStarted = 0;

                workerThreadHolderPtr->mHworkerThread =
                    CreateThread(
                        NULL,
                        STK_SIZE,
                        WorkerThreadFun,
                        (PVOID)workerThreadHolderPtr,
                        STACK_SIZE_PARAM_IS_A_RESERVATION,
                        NULL);
            }
        }

        workerThreadHolderPtr->mGuardThreadState = GRD_READY;

        WakeAllConditionVariable(&workerThreadHolderPtr->mThreadReadyCond);
    }

    return 0;
}

void Start(WorkerThreadHolder *workerThreadHolderPtr)
{
    workerThreadHolderPtr->mGuardThreadState = GRD_IDLE;

    workerThreadHolderPtr->mIsWorkerStarted = 0;

    InitializeConditionVariable(&workerThreadHolderPtr->mThreadReadyCond);
    InitializeConditionVariable(&workerThreadHolderPtr->mStartTaskCond);
    InitializeConditionVariable(&workerThreadHolderPtr->mTaskFinishedCond);
    InitializeCriticalSection(&workerThreadHolderPtr->mLock);

    workerThreadHolderPtr->mHworkerThread =
        CreateThread(
            NULL,
            STK_SIZE,
            WorkerThreadFun,
            (LPVOID)workerThreadHolderPtr,
            STACK_SIZE_PARAM_IS_A_RESERVATION,
            NULL);

    workerThreadHolderPtr->mHworkerGroupThread =
        CreateThread(
            NULL,
            0,
            WorkerGuardThreadFun,
            (LPVOID)workerThreadHolderPtr,
            0,
            NULL);
}

void ExecuteTask(WorkerThreadHolder *workerThreadHolderPtr)
{
    assert(workerThreadHolderPtr->mHworkerThread != NULL);
    assert(workerThreadHolderPtr->mHworkerGroupThread != NULL);

    EnterCriticalSection(&workerThreadHolderPtr->mLock);

    for (;;)
    {
        if (workerThreadHolderPtr->mGuardThreadState == GRD_READY /* && workerThreadHolderPtr->mIsWorkerStarted != 0 */)
            break;

        SleepConditionVariableCS(
            &workerThreadHolderPtr->mThreadReadyCond,
            &workerThreadHolderPtr->mLock,
            INFINITE);
    }

    /* just poll */
    for (;;)
    {
        if (workerThreadHolderPtr->mIsWorkerStarted != 0)
            break;
    }

    workerThreadHolderPtr->mGuardThreadState = GRD_TASKSTARTING;

    WakeAllConditionVariable(&workerThreadHolderPtr->mStartTaskCond);

    LeaveCriticalSection(&workerThreadHolderPtr->mLock);
}

/*----------------------------------------------------------------------------*/

int main(int argc, char *argv[])
{
    int i;
    WorkerThreadHolder workerThreadHolder;

    /*
    HMODULE NTLibrary = GetModuleHandleW(L"ntdll.dll");
    RtlFreeUserThreadStack = (PRtlFreeUserThreadStack)GetProcAddress(NTLibrary, "RtlFreeUserThreadStack");
    */

    Start(&workerThreadHolder);

    for(i = 0;; ++i)
    {
        ExecuteTask(&workerThreadHolder);
        printf("%d Execution started...\n", i);
        /*fflush(stdout);*/
    }

    return 0;
}

Tested with Visual Studio 2015, vc command line: /GS- /analyze- /W3 /Zc:wchar_t /ZI /Gm /Od /Fd"Debug\vc140.pdb" /Zc:inline /fp:precise /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy /MDd /Fa"Debug\" /nologo /Fo"Debug\" /Fp"Debug\ConsoleApplication1.pch"

linker: /OUT:"C:\Users\cherney\documents\visual studio 2015\Projects\ConsoleApplication1\Debug\ConsoleApplication1.exe" /MANIFEST /NXCOMPAT /PDB:"C:\Users\cherney\documents\visual studio 2015\Projects\ConsoleApplication1\Debug\ConsoleApplication1.pdb" /DYNAMICBASE "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /DEBUG /MACHINE:X86 /INCREMENTAL /PGD:"C:\Users\cherney\documents\visual studio 2015\Projects\ConsoleApplication1\Debug\ConsoleApplication1.pgd" /SUBSYSTEM:CONSOLE /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /ManifestFile:"Debug\ConsoleApplication1.exe.intermediate.manifest" /ERRORREPORT:PROMPT /NOLOGO /TLBID:1

Upvotes: 0

Views: 1117

Answers (1)

J.R.
J.R.

Reputation: 1978

'TerminateThread' is 'dangerous' and in fact some de-allocations may not occur (see https://learn.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-terminatethread). Best to redesign the code to cleanly exit threads without the use of 'TerminateThread'.

Upvotes: 4

Related Questions