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
DLL_THREAD_ATTACH
with the intention of freeing it inDLL_THREAD_DETACH
. But terminating a thread does not sendDLL_THREAD_DETACH
. (It can't, since the thread that needs to send it no longer exists.) – Raymond Chen