terminate thread never safe. how minimum this lead to resource leaks. and main - you never can know at which point thread was when you terminate it. it can for example be inside heap alloc/free critical section. and terminate it at this point lead to deadlock on next heap operation (because it critical section will never released).
however exist many correct solutions, how stop I/O. possible of course use 2 special event how already described in comments, but in my opinion this is not the best solution
1) we can use CancelIoEx
on file handle. of course simply call CancelIoEx
not enough - because at this time can be no I/O active in dedicated thread. need yet use special flag (_bQuit
) for task canceled, but even this not enough. need check/set this flag inside critical section or rundown-protection with ReadDirectoryChangesW/CancelIoEx
in dedicated thread
AcquireSRWLockExclusive(this);
if (!_bQuit) // (1)
{
ReadDirectoryChangesW(*); // (4)
}
ReleaseSRWLockExclusive(this);
and for stop
AcquireSRWLockExclusive(this);
_bQuit = true; // (2)
CancelIoEx(*); (3)
ReleaseSRWLockExclusive(this);
without critical section or rundown-protection will be possible execution in next order:
if (!_bQuit) // (1)
_bQuit = true; // (2)
CancelIoEx(*); (3)
ReadDirectoryChangesW(*); // (4)
can be situation when worked thread first check flag _bQuit and it still false. then main thread set flag and call CancelIoEx
which will be have no effect, because no I/O on file. and only then worked thread call ReadDirectoryChangesW
whicj will be not canceled.
but by use critical section (in wide sense) we make this impossible. so possible only 2 orders:
or
if (!_bQuit) ReadDirectoryChangesW(*); // (1)
_bQuit = true; CancelIoEx(*); // (2)
in this case ReadDirectoryChangesW
will be canceled by CancelIoEx
or
_bQuit = true; CancelIoEx(*); // (1)
if (!_bQuit) ReadDirectoryChangesW(*); // (2)
in this case worked thread view _bQuit
flag set and not call ReadDirectoryChangesW
more.
in complete code this can look like:
inline ULONG BOOL_TO_ERROR(BOOL f)
{
return f ? NOERROR : GetLastError();
}
struct WatchFolder : SRWLOCK
{
HANDLE _hThread, _hFile;
BOOLEAN _bQuit;
WatchFolder() : _hThread(0), _hFile(0), _bQuit(false)
{
InitializeSRWLock(this);
}
~WatchFolder()
{
if (_hThread) {
WaitForSingleObject(_hThread, INFINITE);
CloseHandle(_hThread);
}
if (_hFile) CloseHandle(_hFile);
}
static ULONG CALLBACK _WatchDirectory(PVOID This)
{
reinterpret_cast<WatchFolder*>(This)->WatchDirectory();
return 0;
}
void WatchDirectory()
{
OVERLAPPED ov {};
if (ov.hEvent = CreateEvent(0, 0, 0, 0))
{
union {
FILE_NOTIFY_INFORMATION fni;
char buf[0x800];// must be aligned as FILE_NOTIFY_INFORMATION
};
for(;;)
{
AcquireSRWLockExclusive(this);
ULONG dwError = _bQuit ? ERROR_OPERATION_ABORTED : BOOL_TO_ERROR(
ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));
ReleaseSRWLockExclusive(this);
ULONG NumberOfBytesTransferred = 0;
if (dwError == NOERROR)
{
dwError = BOOL_TO_ERROR(GetOverlappedResult(_hFile, &ov, &NumberOfBytesTransferred, TRUE));
}
if (dwError || !NumberOfBytesTransferred)
{
if (dwError != ERROR_OPERATION_ABORTED)
{
__nop();
}
break;
}
FILE_NOTIFY_INFORMATION* pNotify = &fni;
ULONG NextEntryOffset = 0;
do
{
(PBYTE&)pNotify += NextEntryOffset;
DbgPrint("%x %.*S\n", pNotify->Action, pNotify->FileNameLength / sizeof(WCHAR), pNotify->FileName);
} while (NextEntryOffset = pNotify->NextEntryOffset);
}
CloseHandle(ov.hEvent);
}
}
ULONG Start(PCWSTR szFile)
{
HANDLE hFile = CreateFileW(szFile, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, NULL);
ULONG dwError;
if (hFile != INVALID_HANDLE_VALUE)
{
if (_hThread = CreateThread(0, 0, _WatchDirectory, this, 0, 0))
{
_hFile = hFile;
return NOERROR;
}
dwError = GetLastError();
CloseHandle(hFile);
}
else
{
dwError = GetLastError();
}
return dwError;
}
void Stop()
{
AcquireSRWLockExclusive(this);
_bQuit = true, CancelIoEx(_hFile, 0);
ReleaseSRWLockExclusive(this);
}
};
void test()
{
WatchFolder wf;
if (wf.Start(L"somepath") == NOERROR)
{
MessageBoxW(0,0,0,0);
wf.Stop();
}
}
2) another way do this simply call CloseHandle(_hFile)
instead CancelIoEx(_hFile, 0);
. when handle(last, but assume you have only single handle) is closed - system end complete ReadDirectoryChangesW
with status STATUS_NOTIFY_CLEANUP
. code will be very similar to case CancelIoEx
except now error on termination will be ERROR_NOTIFY_CLEANUP
instead ERROR_OPERATION_ABORTED
. but if use GetOverlappedResult[Ex]
exist problem - this api have error in implementation - it lost all positive status values. it simply lost STATUS_NOTIFY_CLEANUP
(but we of course can view it in Internal
field of OVERLAPPED
. code can be next:
AcquireSRWLockExclusive(this);
ULONG dwError = _bQuit ? ERROR_NOTIFY_CLEANUP : BOOL_TO_ERROR(
ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));
ReleaseSRWLockExclusive(this);
ULONG NumberOfBytesTransferred = 0;
if (dwError == NOERROR)
{
dwError = BOOL_TO_ERROR(GetOverlappedResult(_hFile, &ov, &NumberOfBytesTransferred, TRUE));
// fix for error in GetOverlappedResult
if (dwError == NOERROR && ov.Internal) dwError = RtlNtStatusToDosError((NTSTATUS)ov.Internal);
}
if (dwError || !NumberOfBytesTransferred)
{
if (dwError != ERROR_NOTIFY_CLEANUP)
{
__nop();
}
break;
}
and for stop
AcquireSRWLockExclusive(this);
_bQuit = true, CloseHandle(_hFile), _hFile = 0;
ReleaseSRWLockExclusive(this);
3) else one option use alertable wait inside GetOverlappedResultEx
and insert apc (or alert to worked thread). in this case we not need use critical section/or rundown-protection - because no matter will be apc (or alert) inserted before or after call ReadDirectoryChangesW
- it anyway break wait.
ULONG dwError = _bQuit ? STATUS_USER_APC : BOOL_TO_ERROR(
ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));
ULONG NumberOfBytesTransferred = 0;
if (dwError == NOERROR)
{
dwError = BOOL_TO_ERROR(GetOverlappedResultEx(_hFile, &ov, &NumberOfBytesTransferred, INFINITE, TRUE));
}
if (dwError || !NumberOfBytesTransferred)
{
if (dwError == STATUS_USER_APC)
{
CancelIo(_hFile);
GetOverlappedResult(_hFile, &ov, &NumberOfBytesTransferred, TRUE);
}
break;
}
and for stop we need
static VOID NTAPI dummyAPC(_In_ ULONG_PTR )
{
}
_bQuit = true;
QueueUserAPC(dummyAPC, _hThread, 0);
of course instead call dummyAPC
(which not need) better use alert, but GetOverlappedResultEx (more exactly WaitForSingleObjectEx
) ignore STATUS_ALERT
and again begin wait after it interrupted with STATUS_ALERT
. so need use custom code here
ULONG
WINAPI
GetOverlappedResult2( _In_ LPOVERLAPPED lpOverlapped,
_Out_ PULONG_PTR lpNumberOfBytesTransferred)
{
while (lpOverlapped->Internal == STATUS_PENDING)
{
if (NTSTATUS status = ZwWaitForSingleObject(lpOverlapped->hEvent, TRUE, 0))
{
return RtlNtStatusToDosError(status);
}
}
KeMemoryBarrier();
*lpNumberOfBytesTransferred = lpOverlapped->InternalHigh;
return RtlNtStatusToDosError((NTSTATUS)lpOverlapped->Internal);
}
and with it can use next code:
ULONG dwError = _bQuit ? ERROR_ALERTED : BOOL_TO_ERROR(
ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, &ov, 0));
ULONG_PTR NumberOfBytesTransferred = 0;
if (dwError == NOERROR)
{
dwError = GetOverlappedResult2(&ov, &NumberOfBytesTransferred);
}
if (dwError || !NumberOfBytesTransferred)
{
if (dwError == ERROR_ALERTED)
{
CancelIo(_hFile);
GetOverlappedResult(_hFile, &ov, (ULONG*)&NumberOfBytesTransferred, TRUE);
}
break;
}
and for stop
_bQuit = true;
NtAlertThread(_hThread);
4) however the best way for my option - not use dedicated thread all all, but use complete asynchronous I/O. example of code
struct WatchFolderCB : SRWLOCK, OVERLAPPED
{
HANDLE _hFile;
LONG _dwRefCount;
union {
FILE_NOTIFY_INFORMATION fni;
char buf[0x800];// must be aligned as FILE_NOTIFY_INFORMATION
};
BOOLEAN _bQuit;
void AddRef()
{
InterlockedIncrementNoFence(&_dwRefCount);
}
void Release()
{
if (!InterlockedDecrement(&_dwRefCount))
{
delete this;
}
}
WatchFolderCB() : _hFile(0), _bQuit(false), _dwRefCount(1)
{
InitializeSRWLock(this);
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
}
~WatchFolderCB()
{
if (_hFile) CloseHandle(_hFile);
}
static VOID WINAPI _IoCompletionCallback(
_In_ DWORD dwErrorCode,
_In_ DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
)
{
static_cast<WatchFolderCB*>(lpOverlapped)->IoCompletionCallback(
RtlNtStatusToDosError(dwErrorCode), dwNumberOfBytesTransfered);
}
VOID IoCompletionCallback(DWORD dwErrorCode, DWORD NumberOfBytesTransferred)
{
if (dwErrorCode || !NumberOfBytesTransferred)
{
if (dwErrorCode != ERROR_NOTIFY_CLEANUP)
{
__nop();
}
}
else
{
FILE_NOTIFY_INFORMATION* pNotify = &fni;
ULONG NextEntryOffset = 0;
do
{
(PBYTE&)pNotify += NextEntryOffset;
DbgPrint("%x %.*S\n", pNotify->Action, pNotify->FileNameLength / sizeof(WCHAR), pNotify->FileName);
} while (NextEntryOffset = pNotify->NextEntryOffset);
ReadChanges();
}
Release();
}
void ReadChanges()
{
AddRef();
AcquireSRWLockExclusive(this);
ULONG dwError = _bQuit ? ERROR_NOTIFY_CLEANUP : BOOL_TO_ERROR(
ReadDirectoryChangesW(_hFile, buf, sizeof(buf), TRUE, FILE_NOTIFY_VALID_MASK, 0, this, 0));
ReleaseSRWLockExclusive(this);
if (dwError)
{
IoCompletionCallback(dwError, 0);
}
}
ULONG Start(PCWSTR szFile)
{
HANDLE hFile = CreateFileW(szFile, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, NULL);
ULONG dwError;
if (hFile != INVALID_HANDLE_VALUE)
{
if (BindIoCompletionCallback(hFile, _IoCompletionCallback, 0))
{
_hFile = hFile;
ReadChanges();
return NOERROR;
}
dwError = GetLastError();
CloseHandle(hFile);
}
else
{
dwError = GetLastError();
}
return dwError;
}
void Stop()
{
AcquireSRWLockExclusive(this);
_bQuit = true, CloseHandle(_hFile), _hFile = 0;
ReleaseSRWLockExclusive(this);
}
};
void test1()
{
if (WatchFolderCB* p = new WatchFolderCB)
{
if (p->Start(L"*") == NOERROR)
{
MessageBoxW(0,0,0,0);
p->Stop();
}
p->Release();
}
}