diff options
Diffstat (limited to 'src/corelib/io/qwindowspipewriter.cpp')
-rw-r--r-- | src/corelib/io/qwindowspipewriter.cpp | 258 |
1 files changed, 121 insertions, 137 deletions
diff --git a/src/corelib/io/qwindowspipewriter.cpp b/src/corelib/io/qwindowspipewriter.cpp index 5ed584c6e3..9d0f6a8a3e 100644 --- a/src/corelib/io/qwindowspipewriter.cpp +++ b/src/corelib/io/qwindowspipewriter.cpp @@ -1,47 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2021 Alex Trotsenko <alex1973tr@gmail.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2021 Alex Trotsenko <alex1973tr@gmail.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwindowspipewriter_p.h" -#include <qscopedvaluerollback.h> #include <qcoreapplication.h> #include <QMutexLocker> +#include <QPointer> QT_BEGIN_NAMESPACE @@ -49,15 +13,15 @@ QWindowsPipeWriter::QWindowsPipeWriter(HANDLE pipeWriteEnd, QObject *parent) : QObject(parent), handle(pipeWriteEnd), eventHandle(CreateEvent(NULL, FALSE, FALSE, NULL)), - syncHandle(CreateEvent(NULL, FALSE, FALSE, NULL)), + syncHandle(CreateEvent(NULL, TRUE, FALSE, NULL)), waitObject(NULL), pendingBytesWrittenValue(0), lastError(ERROR_SUCCESS), + completionState(NoError), stopped(true), writeSequenceStarted(false), bytesWrittenPending(false), - winEventActPosted(false), - inBytesWritten(false) + winEventActPosted(false) { ZeroMemory(&overlapped, sizeof(OVERLAPPED)); overlapped.hEvent = eventHandle; @@ -75,6 +39,19 @@ QWindowsPipeWriter::~QWindowsPipeWriter() } /*! + Assigns the handle to this writer. The handle must be valid. + Call this function if data was buffered before getting the handle. + */ +void QWindowsPipeWriter::setHandle(HANDLE hPipeWriteEnd) +{ + Q_ASSERT(!stopped); + + handle = hPipeWriteEnd; + QMutexLocker locker(&mutex); + startAsyncWriteHelper(&locker); +} + +/*! Stops the asynchronous write sequence. If the write sequence is running then the I/O operation is canceled. */ @@ -104,59 +81,71 @@ void QWindowsPipeWriter::stop() } /*! - Returns \c true if async operation is in progress or a bytesWritten - signal is pending. + Returns the number of bytes that are waiting to be written. */ -bool QWindowsPipeWriter::isWriteOperationActive() const +qint64 QWindowsPipeWriter::bytesToWrite() const { QMutexLocker locker(&mutex); - return writeSequenceStarted || bytesWrittenPending; + return writeBuffer.size() + pendingBytesWrittenValue; } /*! - Returns the number of bytes that are waiting to be written. + Returns \c true if async operation is in progress. +*/ +bool QWindowsPipeWriter::isWriteOperationActive() const +{ + return completionState == NoError && bytesToWrite() != 0; +} + +/*! + Writes a shallow copy of \a ba to the internal buffer. */ -qint64 QWindowsPipeWriter::bytesToWrite() const +void QWindowsPipeWriter::write(const QByteArray &ba) { - QMutexLocker locker(&mutex); - return writeBuffer.size() + pendingBytesWrittenValue; + if (completionState != WriteDisabled) + writeImpl(ba); } /*! - Writes data to the pipe. + Writes data to the internal buffer. */ -bool QWindowsPipeWriter::write(const QByteArray &ba) +void QWindowsPipeWriter::write(const char *data, qint64 size) +{ + if (completionState != WriteDisabled) + writeImpl(data, size); +} + +template <typename... Args> +inline void QWindowsPipeWriter::writeImpl(Args... args) { QMutexLocker locker(&mutex); - if (lastError != ERROR_SUCCESS) - return false; + writeBuffer.append(args...); - writeBuffer.append(ba); - if (writeSequenceStarted) - return true; + if (writeSequenceStarted || (lastError != ERROR_SUCCESS)) + return; stopped = false; + + // If we don't have an assigned handle yet, defer writing until + // setHandle() is called. + if (handle != INVALID_HANDLE_VALUE) + startAsyncWriteHelper(&locker); +} + +void QWindowsPipeWriter::startAsyncWriteHelper(QMutexLocker<QMutex> *locker) +{ startAsyncWriteLocked(); // Do not post the event, if the write operation will be completed asynchronously. - if (!bytesWrittenPending) - return true; - - if (!winEventActPosted) { - winEventActPosted = true; - locker.unlock(); - QCoreApplication::postEvent(this, new QEvent(QEvent::WinEventAct)); - } else { - locker.unlock(); - } + if (!bytesWrittenPending && lastError == ERROR_SUCCESS) + return; - SetEvent(syncHandle); - return true; + notifyCompleted(locker); } /*! - Starts a new write sequence. Thread-safety should be ensured by the caller. + Starts a new write sequence. */ void QWindowsPipeWriter::startAsyncWriteLocked() { @@ -173,16 +162,17 @@ void QWindowsPipeWriter::startAsyncWriteLocked() // Operation has been queued and will complete in the future. writeSequenceStarted = true; SetThreadpoolWait(waitObject, eventHandle, NULL); - return; + break; } } if (!writeCompleted(errorCode, numberOfBytesWritten)) - return; + break; } } /*! + \internal Thread pool callback procedure. */ void QWindowsPipeWriter::waitCallback(PTP_CALLBACK_INSTANCE instance, PVOID context, @@ -213,17 +203,9 @@ void QWindowsPipeWriter::waitCallback(PTP_CALLBACK_INSTANCE instance, PVOID cont if (pipeWriter->writeCompleted(errorCode, numberOfBytesTransfered)) pipeWriter->startAsyncWriteLocked(); - if (pipeWriter->lastError == ERROR_SUCCESS && !pipeWriter->winEventActPosted) { - pipeWriter->winEventActPosted = true; - locker.unlock(); - QCoreApplication::postEvent(pipeWriter, new QEvent(QEvent::WinEventAct)); - } else { - locker.unlock(); - } - - // We set the event only after unlocking to avoid additional context - // switches due to the released thread immediately running into the lock. - SetEvent(pipeWriter->syncHandle); + // We post the notification even if the write operation failed, + // to unblock the main thread, in case it is waiting for the event. + pipeWriter->notifyCompleted(&locker); } /*! @@ -232,24 +214,46 @@ void QWindowsPipeWriter::waitCallback(PTP_CALLBACK_INSTANCE instance, PVOID cont */ bool QWindowsPipeWriter::writeCompleted(DWORD errorCode, DWORD numberOfBytesWritten) { - if (errorCode == ERROR_SUCCESS) { - Q_ASSERT(numberOfBytesWritten == DWORD(writeBuffer.nextDataBlockSize())); - + switch (errorCode) { + case ERROR_SUCCESS: bytesWrittenPending = true; pendingBytesWrittenValue += numberOfBytesWritten; writeBuffer.free(numberOfBytesWritten); return true; + case ERROR_PIPE_NOT_CONNECTED: // the other end has closed the pipe + case ERROR_OPERATION_ABORTED: // the operation was canceled + case ERROR_NO_DATA: // the pipe is being closed + break; + default: + qErrnoWarning(errorCode, "QWindowsPipeWriter: write failed."); + break; } + // The buffer is not cleared here, because the write progress + // should appear on the main thread synchronously. 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."); return false; } /*! + Posts a notification event to the main thread. + */ +void QWindowsPipeWriter::notifyCompleted(QMutexLocker<QMutex> *locker) +{ + if (!winEventActPosted) { + winEventActPosted = true; + locker->unlock(); + QCoreApplication::postEvent(this, new QEvent(QEvent::WinEventAct)); + } else { + locker->unlock(); + } + + // We set the event only after unlocking to avoid additional context + // switches due to the released thread immediately running into the lock. + SetEvent(syncHandle); +} + +/*! Receives notification that the write operation has completed. */ bool QWindowsPipeWriter::event(QEvent *e) @@ -267,20 +271,20 @@ bool QWindowsPipeWriter::event(QEvent *e) */ bool QWindowsPipeWriter::consumePendingAndEmit(bool allowWinActPosting) { + ResetEvent(syncHandle); 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; + const qint64 numberOfBytesWritten = pendingBytesWrittenValue; + const bool emitBytesWritten = bytesWrittenPending; + if (emitBytesWritten) { + bytesWrittenPending = false; + pendingBytesWrittenValue = 0; + } + const DWORD dwError = lastError; locker.unlock(); @@ -288,47 +292,27 @@ bool QWindowsPipeWriter::consumePendingAndEmit(bool allowWinActPosting) if (stopped) return false; - emit canWrite(); - if (!inBytesWritten) { - QScopedValueRollback<bool> guard(inBytesWritten, true); + // Trigger 'ErrorDetected' state only once. This state must be set before + // emitting the bytesWritten() signal. Otherwise, the write sequence will + // be considered not finished, and we may hang if a slot connected + // to bytesWritten() calls waitForBytesWritten(). + if (dwError != ERROR_SUCCESS && completionState == NoError) { + QPointer<QWindowsPipeWriter> alive(this); + completionState = ErrorDetected; + if (emitBytesWritten) + emit bytesWritten(numberOfBytesWritten); + if (alive) { + writeBuffer.clear(); + completionState = WriteDisabled; + emit writeFailed(); + } + } else if (emitBytesWritten) { emit bytesWritten(numberOfBytesWritten); } - return true; -} - -bool QWindowsPipeWriter::waitForNotification(const QDeadlineTimer &deadline) -{ - do { - DWORD waitRet = WaitForSingleObjectEx(syncHandle, deadline.remainingTime(), TRUE); - if (waitRet == WAIT_OBJECT_0) - return true; - - if (waitRet != WAIT_IO_COMPLETION) - return false; - - // Some I/O completion routine was called. Wait some more. - } while (!deadline.hasExpired()); - - return false; -} - -/*! - 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) -{ - QDeadlineTimer timer(msecs); - - // Make sure that 'syncHandle' was triggered by the thread pool callback. - while (isWriteOperationActive() && waitForNotification(timer)) { - if (consumePendingAndEmit(false)) - return true; - } - - return false; + return emitBytesWritten; } QT_END_NAMESPACE + +#include "moc_qwindowspipewriter_p.cpp" |