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:
Joerg Bornemann 2013-07-03 16:09:16 +02:00 committed by The Qt Project
parent 3f605c8b45
commit ee73f7b7db

View File

@ -83,7 +83,9 @@ class QWinIoCompletionPort : protected QThread
{ {
public: public:
QWinIoCompletionPort() 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")); setObjectName(QLatin1String("I/O completion port thread"));
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
@ -92,13 +94,19 @@ public:
return; return;
} }
hPort = hIOCP; hPort = hIOCP;
hQueueDrainedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!hQueueDrainedEvent) {
qErrnoWarning("CreateEvent failed.");
return;
}
} }
~QWinIoCompletionPort() ~QWinIoCompletionPort()
{ {
PostQueuedCompletionStatus(hPort, 0, 0, NULL); PostQueuedCompletionStatus(hPort, 0, finishThreadKey, NULL);
QThread::wait(); QThread::wait();
CloseHandle(hPort); CloseHandle(hPort);
CloseHandle(hQueueDrainedEvent);
} }
void registerNotifier(QWinOverlappedIoNotifier *notifier) void registerNotifier(QWinOverlappedIoNotifier *notifier)
@ -122,6 +130,14 @@ public:
mutex.unlock(); mutex.unlock();
} }
void drainQueue()
{
QMutexLocker locker(&drainQueueMutex);
ResetEvent(hQueueDrainedEvent);
PostQueuedCompletionStatus(hPort, 0, drainQueueKey, NULL);
WaitForSingleObject(hQueueDrainedEvent, INFINITE);
}
using QThread::isRunning; using QThread::isRunning;
protected: protected:
@ -130,23 +146,34 @@ protected:
DWORD dwBytesRead; DWORD dwBytesRead;
ULONG_PTR pulCompletionKey; ULONG_PTR pulCompletionKey;
OVERLAPPED *overlapped; OVERLAPPED *overlapped;
DWORD msecs = INFINITE;
forever { forever {
BOOL success = GetQueuedCompletionStatus(hPort, BOOL success = GetQueuedCompletionStatus(hPort,
&dwBytesRead, &dwBytesRead,
&pulCompletionKey, &pulCompletionKey,
&overlapped, &overlapped,
INFINITE); msecs);
DWORD errorCode = success ? ERROR_SUCCESS : GetLastError(); DWORD errorCode = success ? ERROR_SUCCESS : GetLastError();
if (!success && !overlapped) { 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."); qErrnoWarning(errorCode, "GetQueuedCompletionStatus failed.");
return; return;
} }
if (success && !(dwBytesRead || pulCompletionKey || overlapped)) { if (pulCompletionKey == finishThreadKey)
// We've posted null values via PostQueuedCompletionStatus to end this thread.
return; return;
if (pulCompletionKey == drainQueueKey) {
// Enter drain mode.
Q_ASSERT(msecs == INFINITE);
msecs = 0;
continue;
} }
QWinOverlappedIoNotifier *notifier = reinterpret_cast<QWinOverlappedIoNotifier *>(pulCompletionKey); QWinOverlappedIoNotifier *notifier = reinterpret_cast<QWinOverlappedIoNotifier *>(pulCompletionKey);
@ -158,9 +185,13 @@ protected:
} }
private: private:
const ULONG_PTR finishThreadKey;
const ULONG_PTR drainQueueKey;
HANDLE hPort; HANDLE hPort;
QSet<QWinOverlappedIoNotifier *> notifiers; QSet<QWinOverlappedIoNotifier *> notifiers;
QMutex mutex; QMutex mutex;
QMutex drainQueueMutex;
HANDLE hQueueDrainedEvent;
}; };
QWinIoCompletionPort *QWinOverlappedIoNotifier::iocp = 0; QWinIoCompletionPort *QWinOverlappedIoNotifier::iocp = 0;
@ -224,6 +255,8 @@ bool QWinOverlappedIoNotifier::waitForNotified(int msecs, OVERLAPPED *overlapped
} }
forever { forever {
if (msecs == 0)
iocp->drainQueue();
DWORD result = WaitForSingleObject(hSemaphore, msecs == -1 ? INFINITE : DWORD(msecs)); DWORD result = WaitForSingleObject(hSemaphore, msecs == -1 ? INFINITE : DWORD(msecs));
if (result == WAIT_OBJECT_0) { if (result == WAIT_OBJECT_0) {
ReleaseSemaphore(hSemaphore, 1, NULL); ReleaseSemaphore(hSemaphore, 1, NULL);