Figure 1 KillThrd Library Functions
KillThrd_CreateThread | Creates a worker thread that can be killed at any time |
KillThrd_Kill | Kills the worker thread |
KillThrd_Close | Frees the resources allocated by KillThrd_CreateThread |
KillThrd_DelayDeath | Delays the death of the worker thread until a known "safe" time |
Figure 2 KILLTHRD
KILLTHRD.H
/******************************************************************************
Module name: KillThrd.H
Notices: By Jeffrey Richter
******************************************************************************/
///////////////////////////////////////////////////////////////////////////////
// The kill thread software exception code
// Useful macro for creating our own software exception codes
#define MAKESOFTWAREEXCEPTION(Severity, Facility, Exception) \
((DWORD) ( \
/* Severity code */ (Severity << 0) | \
/* MS(0) or Cust(1) */ (1 << 29) | \
/* Reserved(0) */ (0 << 28) | \
/* Facility code */ (Facility << 16) | \
/* Exception code */ (Exception << 0)))
// Our very own software exception. This exception is raised
// when a thread is being killed.
#define SE_KILLTHREAD \
MAKESOFTWAREEXCEPTION(ERROR_SEVERITY_ERROR, FACILITY_NULL, 1)
///////////////////////////////////////////////////////////////////////////////
// Functions called by the control thread
// Data structure returned to the control thread.
// The control thread should only ever manipulate the
// m_hThread member directly. Never touch the other members.
typedef struct {
HANDLE m_hThread; // Handle of worker thread
HANDLE m_hmtxControl; // Used to coordinate access the other objects
HANDLE m_hmtxDelay; // Queue killing when owned
HANDLE m_heventEnd; // The killing was queued
} KILLTHRD, *PKILLTHRD;
// Creates a worker thread that can be killed at any time
PKILLTHRD WINAPI KillThrd_CreateThread (
LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
DWORD dwCreationFlags, LPDWORD lpThreadId);
// Kills the worker thread
void WINAPI KillThrd_Kill (PKILLTHRD pkt);
// Frees the resources allocated by KillThrd_CreateThread
VOID WINAPI KillThrd_Close (PKILLTHRD pkt);
///////////////////////////////////////////////////////////////////////////////
// Functions called by the worker thread
// Delays the death of the worker until a known "safe" time
void WINAPI KillThrd_DelayDeath (BOOL fBlock);
///////////////////////////////// End of File /////////////////////////////////
KILLTHRD.CPP
/******************************************************************************
Module name: KillThrd.CPP
Notices: By Jeffrey Richter
Purpose: Functions to kill a worker thread cleanly.
******************************************************************************/
#define STRICT
#include <windows.h>
#include "Process.h" // For _beginthreadex
#include "KillThrd.h"
///////////////////////////////////////////////////////////////////////////////
// Used to store a pointer to the worker thread's internal data structure
// Allocated in KillThrd_CreateThread
static int gs_nTlsIndex = TLS_OUT_OF_INDEXES;
// Internal data structure used by the worker thread.
typedef struct {
HANDLE m_hmtxControl; // Used to coordinate access to
// the other objects
HANDLE m_hmtxDelay; // Delay death when owned
DWORD m_dwDelayCount; // # of times to delay death
HANDLE m_heventEnd; // The killing was queued
LPTHREAD_START_ROUTINE m_lpStartAddress; // Worker thread function
LPVOID m_lpParameter; // Worker thread parameter
} KILLTHRD_WORKERINFO, *PKILLTHRD_WORKERINFO;
///////////////////////////////////////////////////////////////////////////////
// Wraper function for the worker thread. The new thread starts here because
// we need to wrap the call to the actual worker thread function in an SEH
// __try block and to perform cleanup just before the thread dies. The
// address of the KILLTHRD_WORKERINFO structure is saved in
// thread local storage.
static UINT WINAPI KillThrd_ThreadFunc (PVOID pvParam) {
PKILLTHRD_WORKERINFO pktwi = (PKILLTHRD_WORKERINFO) pvParam;
DWORD dwExitCode = 0;
__try {
__try {
// The index is allocated in KillThrd_CreateThread
TlsSetValue(gs_nTlsIndex, pktwi);
dwExitCode = pktwi->m_lpStartAddress(pktwi->m_lpParameter);
}
// If the exception occurs because our thread is forcibly being
// killed, execute our handler (the system does a global unwind first).
__except ((GetExceptionCode() == SE_KILLTHREAD) ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
// Nothing to do in here
}
}
__finally {
// This executes even if the thread is dying.
CloseHandle(pktwi->m_hmtxDelay);
CloseHandle(pktwi->m_heventEnd);
CloseHandle(pktwi->m_hmtxControl);
free(pktwi);
}
return(dwExitCode);
}
///////////////////////////////////////////////////////////////////////////////
// Use this function instead of CreateThread to start a killable thread.
// The parameters to this function match CreateThread. The caller is
// responsible for calling KillThrd_Close to free the allocated resources.
PKILLTHRD WINAPI KillThrd_CreateThread (
LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
DWORD dwCreationFlags, LPDWORD lpThreadId) {
PKILLTHRD pkt = NULL;
PKILLTHRD_WORKERINFO pktwi = NULL;
// If this is the first time this function is called, allocate
// a thread local storage slot.
if (gs_nTlsIndex == TLS_OUT_OF_INDEXES)
gs_nTlsIndex = TlsAlloc();
// Use calloc instead of malloc because it zeros the memory block
// Note: Error check should be done here!
pkt = (PKILLTHRD) calloc(1, sizeof(KILLTHRD));
pktwi = (PKILLTHRD_WORKERINFO) calloc(1, sizeof(KILLTHRD_WORKERINFO));
pktwi->m_lpStartAddress = lpStartAddress;
pktwi->m_lpParameter = lpParameter;
pktwi->m_dwDelayCount = 0;
pktwi->m_hmtxControl = CreateMutex(NULL, FALSE, NULL);
pktwi->m_hmtxDelay = CreateMutex(NULL, FALSE, NULL);
pktwi->m_heventEnd = CreateEvent(NULL, TRUE, FALSE, NULL);
// Duplicate the handles so the control thread and the worker
// thread each have their own set of process-relative handles which
// they are responsible for closing. The actual kernel objects will
// not be destroyed until both handles are closed.
DuplicateHandle(GetCurrentProcess(), pktwi->m_hmtxControl,
GetCurrentProcess(), &pkt->m_hmtxControl, 0, FALSE,
DUPLICATE_SAME_ACCESS);
DuplicateHandle(GetCurrentProcess(), pktwi->m_hmtxDelay,
GetCurrentProcess(), &pkt->m_hmtxDelay, 0, FALSE,
DUPLICATE_SAME_ACCESS);
DuplicateHandle(GetCurrentProcess(), pktwi->m_heventEnd,
GetCurrentProcess(), &pkt->m_heventEnd, 0, FALSE,
DUPLICATE_SAME_ACCESS);
// Start the thread at our wrapper function, KillThrd_ThreadFunc,
// which then calls lpStartAddress
pkt->m_hThread = (HANDLE) _beginthreadex(lpThreadAttributes,
dwStackSize, KillThrd_ThreadFunc, pktwi, dwCreationFlags,
(PUINT) lpThreadId);
// The control thread uses pkt to kill the worker thread using
// KillThrd_Kill and must free this resouce by calling KillThrd_Close.
return(pkt);
}
///////////////////////////////////////////////////////////////////////////////
// When the control thread is done with pkt, it must call this function to
// close the handles and free the memory.
VOID WINAPI KillThrd_Close (PKILLTHRD pkt) {
if (pkt != NULL) {
if (pkt->m_hThread != NULL) CloseHandle(pkt->m_hThread);
if (pkt->m_hmtxDelay != NULL) CloseHandle(pkt->m_hmtxDelay);
if (pkt->m_heventEnd != NULL) CloseHandle(pkt->m_heventEnd);
if (pkt->m_hmtxControl != NULL) CloseHandle(pkt->m_hmtxControl);
free(pkt);
}
}
///////////////////////////////////////////////////////////////////////////////
// Terminate the worker thread by getting it to execute this function
static void WINAPI KillThrd_ForceDeath (void) {
// Get the address of the worker thread's internal data block. This was
// set by KillThrd_ThreadFunc
PKILLTHRD_WORKERINFO pktwi =
(PKILLTHRD_WORKERINFO) TlsGetValue(gs_nTlsIndex);
RaiseException(SE_KILLTHREAD, EXCEPTION_NONCONTINUABLE, 0, NULL);
// RaiseException never returns
}
///////////////////////////////////////////////////////////////////////////////
// Macros used to abstract the instruction pointer register for the various
// CPU platforms.
#if defined(_X86_)
#define PROGCTR(Context) (Context.Eip)
#endif
#if defined(_MIPS_)
#define PROGCTR(Context) (Context.Fir)
#endif
#if defined(_ALPHA_)
#define PROGCTR(Context) (Context.Fir)
#endif
#if defined(_PPC_)
#define PROGCTR(Context) (Context.Iar)
#endif
#if !defined(PROGCTR)
#error Module contains CPU-specific code; modify and recompile.
#endif
///////////////////////////////////////////////////////////////////////////////
// The control thread calls this function to kill a worker thread. If the
// worker thread is not currently protected by KillThrd_DelayDeath, we attempt
// to kill the thread now by suspending it, changing it's instruction pointer
// to KillThrd_ForceDeath, and resuming the thread. Effectively we are raising
// an exception in the worker thread. If the worker thread is protected by
// KillThrd_DelayDeath, we simply set an event and let the thread kill itself
// when it calls KillThrd_DelayDeath(FALSE) and ends its protection.
void WINAPI KillThrd_Kill (PKILLTHRD pkt) {
WaitForSingleObject(pkt->m_hmtxControl, INFINITE);
if (WaitForSingleObject(pkt->m_hmtxDelay, 0) == WAIT_TIMEOUT) {
// The worker is delaying its death, set a flag that the worker
// will check later.
SetEvent(pkt->m_heventEnd);
} else {
// The worker can be terminated now!
CONTEXT context;
// Stop the worker thread
SuspendThread(pkt->m_hThread);
if (WaitForSingleObject(pkt->m_hThread, 0) == WAIT_TIMEOUT) {
// The worker has not yet terminated
// Get the worker thread's current CPU registers
context.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(pkt->m_hThread, &context);
// Change the instruction pointer to our function
PROGCTR(context) = (DWORD) KillThrd_ForceDeath;
SetThreadContext(pkt->m_hThread, &context);
// Resuming the thread forces our function to be called which
// rasies an SE_KILLTHREAD exception in the worker thread.
ResumeThread(pkt->m_hThread);
}
ReleaseMutex(pkt->m_hmtxDelay);
}
ReleaseMutex(pkt->m_hmtxControl);
}
///////////////////////////////////////////////////////////////////////////////
// This function is used to allow the worker thread to protect sections of code
// from termination by the control thread. Call KillThrd_DelayDeath(TRUE) to
// start protection from KillThrd_Kill and KillThrd_DelayDeath(FALSE) to end
// protection. Multiple KillThrd_DelayDeath(TRUE) calls are allowed. A delay
// count is maintained and the thread remains protected until the count is 0.
void WINAPI KillThrd_DelayDeath (BOOL fBlock) {
// Get the address of the worker thread's internal data block. This was
// set by KillThrd_ThreadFunc
PKILLTHRD_WORKERINFO pktwi =
(PKILLTHRD_WORKERINFO) TlsGetValue(gs_nTlsIndex);
WaitForSingleObject(pktwi->m_hmtxControl, INFINITE);
if (fBlock) {
// The worker wants to delay its death
// We get and keep the m_hmtxDelay mutex while protected
// from termination by KillThrd_Kill.
WaitForSingleObject(pktwi->m_hmtxDelay, INFINITE);
// Increment the delay death count
pktwi->m_dwDelayCount++;
} else {
// The worker wants to allow its death
// Decrement the delay death count
pktwi->m_dwDelayCount--;
ReleaseMutex(pktwi->m_hmtxDelay);
// If the delay death count is zero and
if ((pktwi->m_dwDelayCount == 0) &&
(WaitForSingleObject(pktwi->m_heventEnd, 0) == WAIT_OBJECT_0)) {
// The delay death count is zero AND KillThrd_Kill has been called.
// Force us (the worker thread) to terminate now.
KillThrd_ForceDeath();
}
}
ReleaseMutex(pktwi->m_hmtxControl);
}
///////////////////////////////// End of File /////////////////////////////////
Figure 3 KTTEST
KTTEST.CPP
/*****************************************************************************
Module: KTTest.CPP
Notices: By Jeffrey Richter
Purpose: Demonstrates how to kill a thread cleanly.
*****************************************************************************/
#define STRICT
#include <windows.h>
#include <windowsx.h>
#pragma warning(disable: 4001) /* Single line comment */
#include <tchar.h>
#include "resource.h"
#include "KillThrd.h" // Kill Thread functions
//////////////////////////////////////////////////////////////////////////////
// This simple C++ class exists in order to test C++ EH.
class CTestClass {
HWND m_hwnd; // Handle of window
HANDLE m_hThread; // Handle to worker thread's kernel object
public:
CTestClass (HWND hwnd);
~CTestClass ();
// This cast operator simply returns the thread handle
operator HANDLE() const { return(m_hThread); }
};
CTestClass::CTestClass (HWND hwnd) {
m_hwnd = hwnd;
// Create a "real" handle to the thread kernel object
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &m_hThread, 0, FALSE, DUPLICATE_SAME_ACCESS);
ListBox_SetCurSel(m_hwnd,
ListBox_AddString(m_hwnd, _T("C++ object created")));
}
CTestClass::~CTestClass () {
// Close the thread handle
CloseHandle(m_hThread);
ListBox_SetCurSel(m_hwnd,
ListBox_AddString(m_hwnd, _T("C++ object destroyed")));
}
///////////////////////////// HANDLE_DLGMSG Macro ////////////////////////////
// The normal HANDLE_MSG macro in WINDOWSX.H does not work properly for dialog
// boxes because DlgProc's return a BOOL instead of an LRESULT (like
// WndProcs). This HANDLE_DLGMSG macro corrects the problem:
#define HANDLE_DLGMSG(hwnd, message, fn) \
case (message): return (SetDlgMsgResult(hwnd, uMsg, \
HANDLE_##message((hwnd), (wParam), (lParam), (fn))))
//////////////////////////////////////////////////////////////////////////////
BOOL KTTest_OnInitDialog (HWND hwnd, HWND hwndFocus, LPARAM lParam) {
// Disable the "End" button since the worker thread hasn't started yet
EnableWindow(GetDlgItem(hwnd, IDC_END), FALSE);
return(TRUE); // Accept default focus window.
}
//////////////////////////////////////////////////////////////////////////////
// This is the interesting test code. This function even creates a stack-based
// C++ object so that you can see that the object's destructor is called when
// the thread is forcibly terminated.
// Turn off optimizations so that the compiler generates the loop code
// See comment inside function
#pragma optimize("g", off)
DWORD WINAPI WorkerThreadWithoutSEH (LPVOID pvParam) {
HWND hwnd = (HWND) pvParam;
// Windows 95 has a bug which causes an exception to be raised when a
// thread is waiting in a call WaitForSingleObject/WaitForMultipleObjects
// and the thread's instruction pointer is changed. Using the fIsWin95
// flag ensures that the is bug does not expose itself.
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize = sizeof(osvi);
GetVersionEx(&osvi);
BOOL fIsWin95 = (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, _T("Starting Worker thread")));
// Create a C++ object to test C++ EH. The C++ object creates a handle to
// this thread's kernel object.
CTestClass TestClass(hwnd);
ListBox_SetCurSel(hwnd, ListBox_AddString(hwnd, _T("Sleep (3 seconds)")));
Sleep(3000);
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, _T("WaitForSingleObject (3 seconds)")));
// Wait on this thread's kernel object - it will never be signalled.
WaitForSingleObject((HANDLE) TestClass, 3000);
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, _T("WaitForMultipleObjects (3 seconds)")));
HANDLE h = (HANDLE) TestClass;
// Wait on this thread's kernel object - it will never be signalled.
WaitForMultipleObjects(1, &h, TRUE, 3000);
// Don't allow the UI thread to terminate us until our loop terminates
KillThrd_DelayDeath(TRUE);
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, _T("Looping a lot, can't be broken")));
for (int n = 0 ; n < 200000; n++) {
// We can delay the thread's death multiple times.
KillThrd_DelayDeath(TRUE);
// We would usually want to delay our death when using functions
// that wait on CriticalSections or other thread synchronization
// objects so that we don't get them and then die leaving other
// threads suspended forever.
free(malloc(10));
KillThrd_DelayDeath(FALSE);
}
KillThrd_DelayDeath(FALSE);
// The UI thread can terminate this loop at any time.
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, _T("Looping a lot, can be broken")));
// We have to turn off optimizations so that the compiler
// produces code for the loop below.
for (n = 0 ; n < 50000000; n++) {
// Do nothing
}
// We made it to the end of the function
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, _T("Finished looping")));
return(0);
}
// Turn optimizations back on
#pragma optimize("g", on)
//////////////////////////////////////////////////////////////////////////////
DWORD WINAPI WorkerThreadExcFilter (DWORD dwExceptionCode, HWND hwnd) {
// Exception filter used for testing. It adds an entry to the listbox
// when the worker thread is termiante with KillThrd_Kill.
if (dwExceptionCode == SE_KILLTHREAD) {
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, _T("SE_KILLTHREAD detected")));
}
return(EXCEPTION_CONTINUE_SEARCH);
}
//////////////////////////////////////////////////////////////////////////////
// This is the worker thread.
DWORD WINAPI WorkerThread (LPVOID pvParam) {
HWND hwnd = (HWND) pvParam;
DWORD dwExitCode = 0;
__try {
__try {
// Because we can't mix SEH and C++ EH in the same function, call
// another function so that C++ objects are destructed properly.
dwExitCode = WorkerThreadWithoutSEH(pvParam);
}
__except (WorkerThreadExcFilter(GetExceptionCode(), hwnd)) {
// We never get in here because WorkerThreadExcFilter always
// returns EXCEPTION_CONTINUE_SEARCH.
}
}
__finally {
// This executes even if the thread is dying.
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, _T("Inside __finally block")));
}
// This executes only if the thread dies naturally. It does execute
// if KillThrd_Kill is used to kill this thread.
ListBox_SetCurSel(hwnd,
ListBox_AddString(hwnd, _T("Worker thread is dying on its own")));
return(dwExitCode);
}
//////////////////////////////////////////////////////////////////////////////
// Special watchdog thread used for testing. This thread just waits for
// the worker thread to terminate and then adds an entry to the listbox.
DWORD WINAPI WorkerMonitorThread (LPVOID pvParam) {
HANDLE hThread = (HANDLE) pvParam;
HWND hwnd = GetForegroundWindow();
WaitForSingleObject(hThread, INFINITE);
// When the worker thread dies, add an entry to the listbox.
HWND hwndLB = GetDlgItem(hwnd, IDC_PROGRESS);
ListBox_SetCurSel(hwndLB,
ListBox_AddString(hwndLB, _T("--> Worker thread is definitely dead")));
// Re-enable the "Start" button and disable the "End" button
EnableWindow(GetDlgItem(hwnd, IDC_START), TRUE);
EnableWindow(GetDlgItem(hwnd, IDC_END), FALSE);
// Give focus to the "Start" button. NOTE: Because the "Start" button
// was created with another thread, we have to attach our input queues
// together first before calling SetFocus.
AttachThreadInput(GetCurrentThreadId(),
GetWindowThreadProcessId(hwnd, NULL), TRUE);
SetFocus(GetDlgItem(hwnd, IDC_START));
return(0);
}
//////////////////////////////////////////////////////////////////////////////
void KTTest_OnCommand (HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
static PKILLTHRD s_pkt = NULL;
HWND hwndLB = GetDlgItem(hwnd, IDC_PROGRESS);
DWORD dwThreadId;
switch (id) {
case IDC_START:
ListBox_ResetContent(hwndLB);
// Create the worker thread using KillThrd_CreateThread so that
// the thread can be killed cleanly at any time. This function
// allocates a block of memory and returns the pointer. This
// pointer is saved in a static so that we can use it when
// the user presses the "End" button.
s_pkt = KillThrd_CreateThread(NULL, 0, WorkerThread,
hwndLB, 0, &dwThreadId);
// For testing purposes, create a watchdog thread that sleeps
// until the worker thread dies. This thread adds a message to
// the listbox when the worker thread is truely dead.
CloseHandle(CreateThread(NULL, 0, WorkerMonitorThread,
(PVOID) s_pkt->m_hThread, 0, &dwThreadId));
// Disable the "Start" button and enable the "End" button.
EnableWindow(hwndCtl, FALSE);
EnableWindow(GetDlgItem(hwnd, IDC_END), TRUE);
SetFocus(GetDlgItem(hwnd, IDC_END));
break;
case IDC_END:
// The worker thread should die now! Disable the "End" button.
EnableWindow(hwndCtl, FALSE);
// Kill the worker thread passing the address of the block
// returned from KillThrd_CreateThread.
KillThrd_Kill(s_pkt);
// Cleanup the resources. If you'd like, you can use the thread
// handle in this structure BEFORE calling KillThrd_Close
KillThrd_Close(s_pkt);
break;
case IDCANCEL: // Allows dialog box to close
EndDialog(hwnd, id);
break;
}
}
//////////////////////////////////////////////////////////////////////////////
BOOL WINAPI KTTest_DlgProc (HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
// Standard Window's messages
HANDLE_DLGMSG(hwnd, WM_INITDIALOG, KTTest_OnInitDialog);
HANDLE_DLGMSG(hwnd, WM_COMMAND, KTTest_OnCommand);
}
return(FALSE); // We didn't process the message.
}
//////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain (HINSTANCE hinstExe, HINSTANCE hinstPrev,
LPSTR lpszCmdLine, int nCmdShow) {
DialogBox(hinstExe, MAKEINTRESOURCE(IDD_KILLTHRDTEST), NULL,
KTTest_DlgProc);
return(0);
}
//////////////////////////////// End of File /////////////////////////////////
RESOURCE.H
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by KTTest.rc
//
#define IDD_KILLTHRDTEST 101
#define IDC_START 1008
#define IDC_END 1009
#define IDC_PROGRESS 1015
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 104
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1016
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif