diff options
author | Kai Koehne <kai.koehne@qt.io> | 2020-11-19 16:06:05 +0100 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2020-11-20 13:28:31 +0000 |
commit | ce29ce586f06f56a66198b934f8860946380e26e (patch) | |
tree | b6c17c8e4261fd1e40b1d075b10a75bf4e6b5a38 /src/corelib/io/qwindowspipewriter.cpp | |
parent | 5509449daf699958c212f4d14060c2c2af902317 (diff) |
Revert "Allow QWindowsPipe{Reader,Writer} to work with foreign event loops"
This reverts commit ee122077b09430da54ca09750589b37326a22d85.
Reason for revert: This causes QProcess::readAll() to sometimes
return nothing after the process has ended.
Fixes: QTBUG-88624
Change-Id: I34fa27ae7fb38cc7c3a1e8eb2fdae2a5775584c2
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
(cherry picked from commit 23100ee61e33680d20f934dcbc96b57e8da29bf9)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'src/corelib/io/qwindowspipewriter.cpp')
-rw-r--r-- | src/corelib/io/qwindowspipewriter.cpp | 353 |
1 files changed, 117 insertions, 236 deletions
diff --git a/src/corelib/io/qwindowspipewriter.cpp b/src/corelib/io/qwindowspipewriter.cpp index 6cea9f3a5e..e374034a06 100644 --- a/src/corelib/io/qwindowspipewriter.cpp +++ b/src/corelib/io/qwindowspipewriter.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -40,306 +40,187 @@ #include "qwindowspipewriter_p.h" #include "qiodevice_p.h" #include <qscopedvaluerollback.h> -#include <qcoreapplication.h> QT_BEGIN_NAMESPACE +QWindowsPipeWriter::Overlapped::Overlapped(QWindowsPipeWriter *pipeWriter) + : pipeWriter(pipeWriter) +{ +} + +void QWindowsPipeWriter::Overlapped::clear() +{ + ZeroMemory(this, sizeof(OVERLAPPED)); +} + + QWindowsPipeWriter::QWindowsPipeWriter(HANDLE pipeWriteEnd, QObject *parent) : QObject(parent), handle(pipeWriteEnd), - eventHandle(CreateEvent(NULL, FALSE, FALSE, NULL)), - syncHandle(CreateEvent(NULL, TRUE, FALSE, NULL)), - waitObject(NULL), + overlapped(this), pendingBytesWrittenValue(0), - lastError(ERROR_SUCCESS), stopped(true), writeSequenceStarted(false), + notifiedCalled(false), bytesWrittenPending(false), - winEventActPosted(false), inBytesWritten(false) { - ZeroMemory(&overlapped, sizeof(OVERLAPPED)); - overlapped.hEvent = eventHandle; - waitObject = CreateThreadpoolWait(waitCallback, this, NULL); - if (waitObject == NULL) - qErrnoWarning("QWindowsPipeWriter: CreateThreadpollWait failed."); + connect(this, &QWindowsPipeWriter::_q_queueBytesWritten, + this, &QWindowsPipeWriter::emitPendingBytesWrittenValue, Qt::QueuedConnection); } QWindowsPipeWriter::~QWindowsPipeWriter() { stop(); - CloseThreadpoolWait(waitObject); - CloseHandle(eventHandle); - CloseHandle(syncHandle); } -/*! - \internal - Stops the asynchronous write sequence. - If the write sequence is running then the I/O operation is canceled. - */ -void QWindowsPipeWriter::stop() +bool QWindowsPipeWriter::waitForWrite(int msecs) { - if (stopped) - return; - - mutex.lock(); - stopped = true; - if (writeSequenceStarted) { - // Trying to disable callback before canceling the operation. - // Callback invocation is unnecessary here. - SetThreadpoolWait(waitObject, NULL, NULL); - if (!CancelIoEx(handle, &overlapped)) { - const DWORD dwError = GetLastError(); - if (dwError != ERROR_NOT_FOUND) { - qErrnoWarning(dwError, "QWindowsPipeWriter: CancelIoEx on handle %p failed.", - handle); - } - } - writeSequenceStarted = false; + if (bytesWrittenPending) { + emitPendingBytesWrittenValue(); + return true; } - mutex.unlock(); - - WaitForThreadpoolWaitCallbacks(waitObject, TRUE); -} -/*! - \internal - Returns \c true if async operation is in progress or a bytesWritten - signal is pending. - */ -bool QWindowsPipeWriter::isWriteOperationActive() const -{ - QMutexLocker locker(&mutex); - return writeSequenceStarted || bytesWrittenPending; -} - -/*! - \internal - Returns the number of bytes that are waiting to be written. - */ -qint64 QWindowsPipeWriter::bytesToWrite() const -{ - QMutexLocker locker(&mutex); - return writeBuffer.size() + pendingBytesWrittenValue; -} - -/*! - \internal - Writes data to the pipe. - */ -bool QWindowsPipeWriter::write(const QByteArray &ba) -{ - QMutexLocker locker(&mutex); + if (!writeSequenceStarted) + return false; - if (lastError != ERROR_SUCCESS) + if (!waitForNotification(msecs)) return false; - writeBuffer.append(ba); - if (writeSequenceStarted) + if (bytesWrittenPending) { + emitPendingBytesWrittenValue(); return true; - - stopped = false; - startAsyncWriteLocked(); - - // Do not post the event, if the write operation will be completed asynchronously. - if (bytesWrittenPending && !winEventActPosted) { - winEventActPosted = true; - locker.unlock(); - QCoreApplication::postEvent(this, new QEvent(QEvent::WinEventAct)); } - return true; -} -/*! - \internal - Starts a new write sequence. Thread-safety should be ensured by the caller. - */ -void QWindowsPipeWriter::startAsyncWriteLocked() -{ - forever { - if (writeBuffer.isEmpty()) - return; - - // WriteFile() returns true, if the write operation completes synchronously. - // We don't need to call GetOverlappedResult() additionally, because - // 'numberOfBytesWritten' is valid in this case. - DWORD numberOfBytesWritten; - if (!WriteFile(handle, writeBuffer.readPointer(), writeBuffer.nextDataBlockSize(), - &numberOfBytesWritten, &overlapped)) { - break; - } - - writeCompleted(ERROR_SUCCESS, numberOfBytesWritten); - } - - const DWORD dwError = GetLastError(); - if (dwError == ERROR_IO_PENDING) { - // Operation has been queued and will complete in the future. - writeSequenceStarted = true; - SetThreadpoolWait(waitObject, eventHandle, NULL); - } else { - // Other return values are actual errors. - writeCompleted(dwError, 0); - } + return false; } -/*! - \internal - Thread pool callback procedure. - */ -void QWindowsPipeWriter::waitCallback(PTP_CALLBACK_INSTANCE instance, PVOID context, - PTP_WAIT wait, TP_WAIT_RESULT waitResult) +qint64 QWindowsPipeWriter::bytesToWrite() const { - Q_UNUSED(instance); - Q_UNUSED(wait); - Q_UNUSED(waitResult); - QWindowsPipeWriter *pipeWriter = reinterpret_cast<QWindowsPipeWriter *>(context); - - // Get the result of the asynchronous operation. - DWORD numberOfBytesTransfered = 0; - DWORD errorCode = ERROR_SUCCESS; - if (!GetOverlappedResult(pipeWriter->handle, &pipeWriter->overlapped, - &numberOfBytesTransfered, FALSE)) - errorCode = GetLastError(); - - QMutexLocker locker(&pipeWriter->mutex); - - // After the writer was stopped, the only reason why this function can be called is the - // completion of a cancellation. No signals should be emitted, and no new write sequence - // should be started in this case. - if (pipeWriter->stopped) - return; - - pipeWriter->writeSequenceStarted = false; - pipeWriter->writeCompleted(errorCode, numberOfBytesTransfered); - if (pipeWriter->lastError != ERROR_SUCCESS) - return; - - pipeWriter->startAsyncWriteLocked(); - - if (!pipeWriter->winEventActPosted) { - pipeWriter->winEventActPosted = true; - locker.unlock(); - QCoreApplication::postEvent(pipeWriter, new QEvent(QEvent::WinEventAct)); - } else { - locker.unlock(); - } - SetEvent(pipeWriter->syncHandle); + return buffer.size() + pendingBytesWrittenValue; } -/*! - \internal - Will be called whenever the write operation completes. - */ -void QWindowsPipeWriter::writeCompleted(DWORD errorCode, DWORD numberOfBytesWritten) +void QWindowsPipeWriter::emitPendingBytesWrittenValue() { - if (errorCode == ERROR_SUCCESS) { - Q_ASSERT(numberOfBytesWritten == DWORD(writeBuffer.nextDataBlockSize())); - - bytesWrittenPending = true; - pendingBytesWrittenValue += numberOfBytesWritten; - writeBuffer.free(numberOfBytesWritten); - } else { - lastError = errorCode; - writeBuffer.clear(); - // The other end has closed the pipe. This can happen in QLocalSocket. Do not warn. - if (errorCode != ERROR_OPERATION_ABORTED && errorCode != ERROR_NO_DATA) - qErrnoWarning(errorCode, "QWindowsPipeWriter: write failed."); + if (bytesWrittenPending) { + // Reset the state even if we don't emit bytesWritten(). + // It's a defined behavior to not re-emit this signal recursively. + bytesWrittenPending = false; + const qint64 bytes = pendingBytesWrittenValue; + pendingBytesWrittenValue = 0; + + emit canWrite(); + if (!inBytesWritten) { + QScopedValueRollback<bool> guard(inBytesWritten, true); + emit bytesWritten(bytes); + } } } -/*! - \internal - Receives notification that the write operation has completed. - */ -bool QWindowsPipeWriter::event(QEvent *e) +void QWindowsPipeWriter::writeFileCompleted(DWORD errorCode, DWORD numberOfBytesTransfered, + OVERLAPPED *overlappedBase) { - if (e->type() == QEvent::WinEventAct) { - emitPendingSignals(true); - return true; - } - return QObject::event(e); + Overlapped *overlapped = static_cast<Overlapped *>(overlappedBase); + overlapped->pipeWriter->notified(errorCode, numberOfBytesTransfered); } /*! \internal - Emits pending signals in the main thread. Returns \c true, - if bytesWritten() was emitted. + Will be called whenever the write operation completes. */ -bool QWindowsPipeWriter::emitPendingSignals(bool allowWinActPosting) +void QWindowsPipeWriter::notified(DWORD errorCode, DWORD numberOfBytesWritten) { - QMutexLocker locker(&mutex); - - // Enable QEvent::WinEventAct posting. - if (allowWinActPosting) - winEventActPosted = false; - - if (!bytesWrittenPending) - return false; - - // Reset the state even if we don't emit bytesWritten(). - // It's a defined behavior to not re-emit this signal recursively. - bytesWrittenPending = false; - qint64 numberOfBytesWritten = pendingBytesWrittenValue; - pendingBytesWrittenValue = 0; - - locker.unlock(); + notifiedCalled = true; + writeSequenceStarted = false; + Q_ASSERT(errorCode != ERROR_SUCCESS || numberOfBytesWritten == DWORD(buffer.size())); + buffer.clear(); + + switch (errorCode) { + case ERROR_SUCCESS: + break; + case ERROR_OPERATION_ABORTED: + if (stopped) + break; + Q_FALLTHROUGH(); + default: + qErrnoWarning(errorCode, "QWindowsPipeWriter: asynchronous write failed."); + break; + } - // Disable any further processing, if the pipe was stopped. + // After the writer was stopped, the only reason why this function can be called is the + // completion of a cancellation. No signals should be emitted, and no new write sequence should + // be started in this case. if (stopped) - return false; + return; - emit canWrite(); - if (!inBytesWritten) { - QScopedValueRollback<bool> guard(inBytesWritten, true); - emit bytesWritten(numberOfBytesWritten); + pendingBytesWrittenValue += qint64(numberOfBytesWritten); + if (!bytesWrittenPending) { + bytesWrittenPending = true; + emit _q_queueBytesWritten(QWindowsPipeWriter::QPrivateSignal()); } - - return true; } bool QWindowsPipeWriter::waitForNotification(int timeout) { QElapsedTimer t; t.start(); + notifiedCalled = false; int msecs = timeout; - do { - DWORD waitRet = WaitForSingleObjectEx(syncHandle, - msecs == -1 ? INFINITE : msecs, TRUE); - if (waitRet == WAIT_OBJECT_0) + while (SleepEx(msecs == -1 ? INFINITE : msecs, TRUE) == WAIT_IO_COMPLETION) { + if (notifiedCalled) return true; - if (waitRet != WAIT_IO_COMPLETION) - return false; - - // Some I/O completion routine was called. Wait some more. + // Some other I/O completion routine was called. Wait some more. msecs = qt_subtract_from_timeout(timeout, t.elapsed()); - } while (msecs != 0); - - return false; + if (!msecs) + break; + } + return notifiedCalled; } -/*! - \internal - Waits for the completion of the asynchronous write operation. - Returns \c true, if we've emitted the bytesWritten signal (non-recursive case) - or bytesWritten will be emitted by the event loop (recursive case). - */ -bool QWindowsPipeWriter::waitForWrite(int msecs) +bool QWindowsPipeWriter::write(const QByteArray &ba) { - // Prepare handle for waiting. - ResetEvent(syncHandle); + if (writeSequenceStarted) + return false; - // It is necessary to check if there is already pending signal. - if (emitPendingSignals(false)) - return true; + overlapped.clear(); + buffer = ba; + stopped = false; + writeSequenceStarted = true; + if (!WriteFileEx(handle, buffer.constData(), buffer.size(), + &overlapped, &writeFileCompleted)) { + writeSequenceStarted = false; + buffer.clear(); - // Make sure that 'syncHandle' was triggered by the thread pool callback. - if (!isWriteOperationActive() || !waitForNotification(msecs)) + const DWORD errorCode = GetLastError(); + switch (errorCode) { + case ERROR_NO_DATA: // "The pipe is being closed." + // The other end has closed the pipe. This can happen in QLocalSocket. Do not warn. + break; + default: + qErrnoWarning(errorCode, "QWindowsPipeWriter::write failed."); + } return false; + } - return emitPendingSignals(false); + return true; +} + +void QWindowsPipeWriter::stop() +{ + stopped = true; + bytesWrittenPending = false; + pendingBytesWrittenValue = 0; + if (writeSequenceStarted) { + if (!CancelIoEx(handle, &overlapped)) { + const DWORD dwError = GetLastError(); + if (dwError != ERROR_NOT_FOUND) { + qErrnoWarning(dwError, "QWindowsPipeWriter: CancelIoEx on handle %p failed.", + handle); + } + } + waitForNotification(-1); + } } QT_END_NAMESPACE |