QWinOverlappedIoNotifier: fix race condition
Fix race condition in waitForNotified for zero timeout. A waitForNotified(0) call is typically used for triggering events near the end of life of the I/O resource (e.g. drainOutputPipes in QProcess). In that case we must synchronize the IOCP thread and waitForNotified. Task-number: QTBUG-29391 Change-Id: Iadbd8564ec461cbc48a6ef8c5627e30a5c6eecfd Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
This commit is contained in:
parent
3f605c8b45
commit
ee73f7b7db
@ -83,7 +83,9 @@ class QWinIoCompletionPort : protected QThread
|
||||
{
|
||||
public:
|
||||
QWinIoCompletionPort()
|
||||
: hPort(INVALID_HANDLE_VALUE)
|
||||
: finishThreadKey(reinterpret_cast<ULONG_PTR>(this)),
|
||||
drainQueueKey(reinterpret_cast<ULONG_PTR>(this + 1)),
|
||||
hPort(INVALID_HANDLE_VALUE)
|
||||
{
|
||||
setObjectName(QLatin1String("I/O completion port thread"));
|
||||
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
||||
@ -92,13 +94,19 @@ public:
|
||||
return;
|
||||
}
|
||||
hPort = hIOCP;
|
||||
hQueueDrainedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (!hQueueDrainedEvent) {
|
||||
qErrnoWarning("CreateEvent failed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
~QWinIoCompletionPort()
|
||||
{
|
||||
PostQueuedCompletionStatus(hPort, 0, 0, NULL);
|
||||
PostQueuedCompletionStatus(hPort, 0, finishThreadKey, NULL);
|
||||
QThread::wait();
|
||||
CloseHandle(hPort);
|
||||
CloseHandle(hQueueDrainedEvent);
|
||||
}
|
||||
|
||||
void registerNotifier(QWinOverlappedIoNotifier *notifier)
|
||||
@ -122,6 +130,14 @@ public:
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
void drainQueue()
|
||||
{
|
||||
QMutexLocker locker(&drainQueueMutex);
|
||||
ResetEvent(hQueueDrainedEvent);
|
||||
PostQueuedCompletionStatus(hPort, 0, drainQueueKey, NULL);
|
||||
WaitForSingleObject(hQueueDrainedEvent, INFINITE);
|
||||
}
|
||||
|
||||
using QThread::isRunning;
|
||||
|
||||
protected:
|
||||
@ -130,23 +146,34 @@ protected:
|
||||
DWORD dwBytesRead;
|
||||
ULONG_PTR pulCompletionKey;
|
||||
OVERLAPPED *overlapped;
|
||||
DWORD msecs = INFINITE;
|
||||
|
||||
forever {
|
||||
BOOL success = GetQueuedCompletionStatus(hPort,
|
||||
&dwBytesRead,
|
||||
&pulCompletionKey,
|
||||
&overlapped,
|
||||
INFINITE);
|
||||
msecs);
|
||||
|
||||
DWORD errorCode = success ? ERROR_SUCCESS : GetLastError();
|
||||
if (!success && !overlapped) {
|
||||
if (!msecs) {
|
||||
// Time out in drain mode. The completion status queue is empty.
|
||||
msecs = INFINITE;
|
||||
SetEvent(hQueueDrainedEvent);
|
||||
continue;
|
||||
}
|
||||
qErrnoWarning(errorCode, "GetQueuedCompletionStatus failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (success && !(dwBytesRead || pulCompletionKey || overlapped)) {
|
||||
// We've posted null values via PostQueuedCompletionStatus to end this thread.
|
||||
if (pulCompletionKey == finishThreadKey)
|
||||
return;
|
||||
if (pulCompletionKey == drainQueueKey) {
|
||||
// Enter drain mode.
|
||||
Q_ASSERT(msecs == INFINITE);
|
||||
msecs = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
QWinOverlappedIoNotifier *notifier = reinterpret_cast<QWinOverlappedIoNotifier *>(pulCompletionKey);
|
||||
@ -158,9 +185,13 @@ protected:
|
||||
}
|
||||
|
||||
private:
|
||||
const ULONG_PTR finishThreadKey;
|
||||
const ULONG_PTR drainQueueKey;
|
||||
HANDLE hPort;
|
||||
QSet<QWinOverlappedIoNotifier *> notifiers;
|
||||
QMutex mutex;
|
||||
QMutex drainQueueMutex;
|
||||
HANDLE hQueueDrainedEvent;
|
||||
};
|
||||
|
||||
QWinIoCompletionPort *QWinOverlappedIoNotifier::iocp = 0;
|
||||
@ -224,6 +255,8 @@ bool QWinOverlappedIoNotifier::waitForNotified(int msecs, OVERLAPPED *overlapped
|
||||
}
|
||||
|
||||
forever {
|
||||
if (msecs == 0)
|
||||
iocp->drainQueue();
|
||||
DWORD result = WaitForSingleObject(hSemaphore, msecs == -1 ? INFINITE : DWORD(msecs));
|
||||
if (result == WAIT_OBJECT_0) {
|
||||
ReleaseSemaphore(hSemaphore, 1, NULL);
|
||||
|
Loading…
Reference in New Issue
Block a user