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:
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);