diff options
Diffstat (limited to 'src/network/socket/qlocalsocket_win.cpp')
-rw-r--r-- | src/network/socket/qlocalsocket_win.cpp | 353 |
1 files changed, 218 insertions, 135 deletions
diff --git a/src/network/socket/qlocalsocket_win.cpp b/src/network/socket/qlocalsocket_win.cpp index 66eed86501..b3f3f9002a 100644 --- a/src/network/socket/qlocalsocket_win.cpp +++ b/src/network/socket/qlocalsocket_win.cpp @@ -1,47 +1,80 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork 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. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qlocalsocket_p.h" #include <qscopedvaluerollback.h> +#include <qdeadlinetimer.h> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + +namespace { +struct QSocketPoller +{ + QSocketPoller(const QLocalSocketPrivate &socket); + + qint64 getRemainingTime(const QDeadlineTimer &deadline) const; + bool poll(const QDeadlineTimer &deadline); + + enum { maxHandles = 2 }; + HANDLE handles[maxHandles]; + DWORD handleCount = 0; + bool waitForClose = false; + bool writePending = false; +}; + +QSocketPoller::QSocketPoller(const QLocalSocketPrivate &socket) +{ + if (socket.pipeWriter && socket.pipeWriter->isWriteOperationActive()) { + handles[handleCount++] = socket.pipeWriter->syncEvent(); + writePending = true; + } + if (socket.pipeReader->isReadOperationActive()) + handles[handleCount++] = socket.pipeReader->syncEvent(); + else + waitForClose = true; +} + +qint64 QSocketPoller::getRemainingTime(const QDeadlineTimer &deadline) const +{ + const qint64 sleepTime = 10; + qint64 remainingTime = deadline.remainingTime(); + if (waitForClose && (remainingTime > sleepTime || remainingTime == -1)) + return sleepTime; + + return remainingTime; +} + +/*! + \internal + + Waits until new data is available for reading or write operation + completes. Returns \c true, if we need to check pipe workers; + otherwise it returns \c false (if an error occurred or the operation + timed out). + + \note If the read operation is inactive, it succeeds after + a short wait, allowing the caller to check the state of the socket. +*/ +bool QSocketPoller::poll(const QDeadlineTimer &deadline) +{ + Q_ASSERT(handleCount != 0); + QDeadlineTimer timer(getRemainingTime(deadline)); + DWORD waitRet; + + do { + waitRet = WaitForMultipleObjectsEx(handleCount, handles, FALSE, + timer.remainingTime(), TRUE); + } while (waitRet == WAIT_IO_COMPLETION); + + if (waitRet == WAIT_TIMEOUT) + return waitForClose || !deadline.hasExpired(); + + return waitRet - WAIT_OBJECT_0 < handleCount; +} +} // anonymous namespace + void QLocalSocketPrivate::init() { Q_Q(QLocalSocket); @@ -105,20 +138,13 @@ QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), emittedReadyRead(false), emittedBytesWritten(false) { - writeBufferChunkSize = QIODEVICE_BUFFERSIZE; } QLocalSocketPrivate::~QLocalSocketPrivate() { - destroyPipeHandles(); -} - -void QLocalSocketPrivate::destroyPipeHandles() -{ - if (handle != INVALID_HANDLE_VALUE) { - DisconnectNamedPipe(handle); - CloseHandle(handle); - } + Q_ASSERT(state == QLocalSocket::UnconnectedState); + Q_ASSERT(handle == INVALID_HANDLE_VALUE); + Q_ASSERT(pipeWriter == nullptr); } void QLocalSocket::connectToServer(OpenMode openMode) @@ -137,14 +163,14 @@ void QLocalSocket::connectToServer(OpenMode openMode) emit stateChanged(d->state); if (d->serverName.isEmpty()) { d->error = ServerNotFoundError; - d->errorString = tr("%1: Invalid name").arg(QLatin1String("QLocalSocket::connectToServer")); + d->errorString = tr("%1: Invalid name").arg("QLocalSocket::connectToServer"_L1); d->state = UnconnectedState; emit errorOccurred(d->error); emit stateChanged(d->state); return; } - const QLatin1String pipePath("\\\\.\\pipe\\"); + const auto pipePath = "\\\\.\\pipe\\"_L1; if (d->serverName.startsWith(pipePath)) d->fullServerName = d->serverName; else @@ -177,7 +203,7 @@ void QLocalSocket::connectToServer(OpenMode openMode) if (localSocket == INVALID_HANDLE_VALUE) { const DWORD winError = GetLastError(); - d->_q_winError(winError, QLatin1String("QLocalSocket::connectToServer")); + d->_q_winError(winError, "QLocalSocket::connectToServer"_L1); d->fullServerName = QString(); return; } @@ -187,6 +213,20 @@ void QLocalSocket::connectToServer(OpenMode openMode) emit connected(); } +static qint64 transformPipeReaderResult(qint64 res) +{ + // QWindowsPipeReader's reading functions return error codes + // that don't match what we need. + switch (res) { + case 0: // EOF -> transform to error + return -1; + case -2: // EWOULDBLOCK -> no error, just no bytes + return 0; + default: + return res; + } +} + // This is reading from the buffer qint64 QLocalSocket::readData(char *data, qint64 maxSize) { @@ -195,22 +235,29 @@ qint64 QLocalSocket::readData(char *data, qint64 maxSize) if (!maxSize) return 0; - qint64 ret = d->pipeReader->read(data, maxSize); + return transformPipeReaderResult(d->pipeReader->read(data, maxSize)); +} - // QWindowsPipeReader::read() returns error codes that don't match what we need - switch (ret) { - case 0: // EOF -> transform to error - return -1; - case -2: // EWOULDBLOCK -> no error, just no bytes +qint64 QLocalSocket::readLineData(char *data, qint64 maxSize) +{ + Q_D(QLocalSocket); + + if (!maxSize) return 0; - default: - return ret; - } + + // QIODevice::readLine() reserves space for the trailing '\0' byte, + // so we must read 'maxSize + 1' bytes. + return transformPipeReaderResult(d->pipeReader->readLine(data, maxSize + 1)); } qint64 QLocalSocket::skipData(qint64 maxSize) { - return QIODevice::skipData(maxSize); + Q_D(QLocalSocket); + + if (!maxSize) + return 0; + + return transformPipeReaderResult(d->pipeReader->skip(maxSize)); } qint64 QLocalSocket::writeData(const char *data, qint64 len) @@ -224,13 +271,20 @@ qint64 QLocalSocket::writeData(const char *data, qint64 len) if (len == 0) return 0; - d->write(data, len); + if (!d->pipeWriter) { d->pipeWriter = new QWindowsPipeWriter(d->handle, this); QObjectPrivate::connect(d->pipeWriter, &QWindowsPipeWriter::bytesWritten, d, &QLocalSocketPrivate::_q_bytesWritten); + QObjectPrivate::connect(d->pipeWriter, &QWindowsPipeWriter::writeFailed, + d, &QLocalSocketPrivate::_q_writeFailed); } - d->_q_canWrite(); + + if (d->isWriteChunkCached(data, len)) + d->pipeWriter->write(*(d->currentWriteChunk)); + else + d->pipeWriter->write(data, len); + return len; } @@ -259,22 +313,28 @@ void QLocalSocketPrivate::_q_pipeClosed() if (state == QLocalSocket::UnconnectedState) return; - emit q->readChannelFinished(); if (state != QLocalSocket::ClosingState) { state = QLocalSocket::ClosingState; emit q->stateChanged(state); if (state != QLocalSocket::ClosingState) return; } - state = QLocalSocket::UnconnectedState; - emit q->stateChanged(state); - emit q->disconnected(); + serverName.clear(); + fullServerName.clear(); pipeReader->stop(); delete pipeWriter; pipeWriter = nullptr; - destroyPipeHandles(); - handle = INVALID_HANDLE_VALUE; + if (handle != INVALID_HANDLE_VALUE) { + DisconnectNamedPipe(handle); + CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; + } + + state = QLocalSocket::UnconnectedState; + emit q->stateChanged(state); + emit q->readChannelFinished(); + emit q->disconnected(); } qint64 QLocalSocket::bytesAvailable() const @@ -288,7 +348,7 @@ qint64 QLocalSocket::bytesAvailable() const qint64 QLocalSocket::bytesToWrite() const { Q_D(const QLocalSocket); - return d->writeBuffer.size() + (d->pipeWriter ? d->pipeWriter->bytesToWrite() : 0); + return d->pipeWriterBytesToWrite(); } bool QLocalSocket::canReadLine() const @@ -300,52 +360,30 @@ bool QLocalSocket::canReadLine() const void QLocalSocket::close() { Q_D(QLocalSocket); - if (openMode() == NotOpen) - return; - d->setWriteChannelCount(0); QIODevice::close(); + d->pipeReader->stopAndClear(); d->serverName = QString(); d->fullServerName = QString(); - - if (state() != UnconnectedState) { - if (bytesToWrite() > 0) { - disconnectFromServer(); - return; - } - - d->_q_pipeClosed(); - } + disconnectFromServer(); } bool QLocalSocket::flush() { Q_D(QLocalSocket); - bool written = false; - while (d->pipeWriter && d->pipeWriter->waitForWrite(0)) - written = true; - return written; + + return d->pipeWriter && d->pipeWriter->checkForWrite(); } void QLocalSocket::disconnectFromServer() { Q_D(QLocalSocket); - // Are we still connected? - if (!isValid()) { - // If we have unwritten data, the pipeWriter is still present. - // It must be destroyed before close() to prevent an infinite loop. - delete d->pipeWriter; - d->pipeWriter = 0; - d->writeBuffer.clear(); - } - - flush(); - if (bytesToWrite() != 0) { + if (bytesToWrite() == 0) { + d->_q_pipeClosed(); + } else if (d->state != QLocalSocket::ClosingState) { d->state = QLocalSocket::ClosingState; emit stateChanged(d->state); - } else { - close(); } } @@ -370,6 +408,11 @@ bool QLocalSocket::setSocketDescriptor(qintptr socketDescriptor, return true; } +qint64 QLocalSocketPrivate::pipeWriterBytesToWrite() const +{ + return pipeWriter ? pipeWriter->bytesToWrite() : qint64(0); +} + void QLocalSocketPrivate::_q_bytesWritten(qint64 bytes) { Q_Q(QLocalSocket); @@ -377,20 +420,18 @@ void QLocalSocketPrivate::_q_bytesWritten(qint64 bytes) QScopedValueRollback<bool> guard(emittedBytesWritten, true); emit q->bytesWritten(bytes); } - _q_canWrite(); + if (state == QLocalSocket::ClosingState) + q->disconnectFromServer(); } -void QLocalSocketPrivate::_q_canWrite() +void QLocalSocketPrivate::_q_writeFailed() { Q_Q(QLocalSocket); - if (writeBuffer.isEmpty()) { - if (state == QLocalSocket::ClosingState) - q->close(); - } else { - Q_ASSERT(pipeWriter); - if (!pipeWriter->isWriteOperationActive()) - pipeWriter->write(writeBuffer.read()); - } + error = QLocalSocket::PeerClosedError; + errorString = QLocalSocket::tr("Remote closed"); + emit q->errorOccurred(error); + + _q_pipeClosed(); } qintptr QLocalSocket::socketDescriptor() const @@ -424,15 +465,38 @@ bool QLocalSocket::waitForDisconnected(int msecs) qWarning("QLocalSocket::waitForDisconnected() is not allowed in UnconnectedState"); return false; } - if (!openMode().testFlag(QIODevice::ReadOnly)) { - qWarning("QLocalSocket::waitForDisconnected isn't supported for write only pipes."); - return false; - } - if (d->pipeReader->waitForPipeClosed(msecs)) { - d->_q_pipeClosed(); - return true; + + QDeadlineTimer deadline(msecs); + bool wasChecked = false; + while (!d->pipeReader->isPipeClosed()) { + if (wasChecked && deadline.hasExpired()) + return false; + + QSocketPoller poller(*d); + // The first parameter of the WaitForMultipleObjectsEx() call cannot + // be zero. So we have to call SleepEx() here. + if (!poller.writePending && poller.waitForClose) { + // Prevent waiting on the first pass, if both the pipe reader + // and the pipe writer are inactive. + if (wasChecked) + SleepEx(poller.getRemainingTime(deadline), TRUE); + } else if (!poller.poll(deadline)) { + return false; + } + + if (d->pipeWriter) + d->pipeWriter->checkForWrite(); + + // When the read buffer is full, the read sequence is not running, + // so we need to peek the pipe to detect disconnection. + if (poller.waitForClose && isValid()) + d->pipeReader->checkPipeState(); + + d->pipeReader->checkForReadyRead(); + wasChecked = true; } - return false; + d->_q_pipeClosed(); + return true; } bool QLocalSocket::isValid() const @@ -448,32 +512,51 @@ bool QLocalSocket::waitForReadyRead(int msecs) if (d->state != QLocalSocket::ConnectedState) return false; - // We already know that the pipe is gone, but did not enter the event loop yet. - if (d->pipeReader->isPipeClosed()) { - d->_q_pipeClosed(); - return false; - } - - bool result = d->pipeReader->waitForReadyRead(msecs); + QDeadlineTimer deadline(msecs); + while (!d->pipeReader->isPipeClosed()) { + QSocketPoller poller(*d); + if (poller.waitForClose || !poller.poll(deadline)) + return false; - // We just noticed that the pipe is gone. - if (d->pipeReader->isPipeClosed()) - d->_q_pipeClosed(); + if (d->pipeWriter) + d->pipeWriter->checkForWrite(); - return result; + if (d->pipeReader->checkForReadyRead()) + return true; + } + d->_q_pipeClosed(); + return false; } bool QLocalSocket::waitForBytesWritten(int msecs) { - Q_D(const QLocalSocket); - if (!d->pipeWriter) + Q_D(QLocalSocket); + + if (d->state == QLocalSocket::UnconnectedState) return false; - // Wait for the pipe writer to acknowledge that it has - // written. This will succeed if either the pipe writer has - // already written the data, or if it manages to write data - // within the given timeout. - return d->pipeWriter->waitForWrite(msecs); + QDeadlineTimer deadline(msecs); + bool wasChecked = false; + while (!d->pipeReader->isPipeClosed()) { + if (wasChecked && deadline.hasExpired()) + return false; + + QSocketPoller poller(*d); + if (!poller.writePending || !poller.poll(deadline)) + return false; + + Q_ASSERT(d->pipeWriter); + if (d->pipeWriter->checkForWrite()) + return true; + + if (poller.waitForClose && isValid()) + d->pipeReader->checkPipeState(); + + d->pipeReader->checkForReadyRead(); + wasChecked = true; + } + d->_q_pipeClosed(); + return false; } QT_END_NAMESPACE |