QLocalSocket/Win: fix closed state detection in waitFor...() functions

A delayed close should only be completed in the _q_bytesWritten() slot
as a confirmation of a successful write operation on the socket.
Otherwise, a failed write operation may cause the socket to be closed
unexpectedly within the waitFor...() function, which may result in a
malfunction.

Change-Id: I14cff26734f64a89090b6b5c13037466a6400597
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
This commit is contained in:
Alex Trotsenko 2021-05-12 19:25:15 +03:00 committed by Oswald Buddenhagen
parent 91c65dd80c
commit 9c7cabb880
4 changed files with 32 additions and 25 deletions

View File

@ -58,7 +58,7 @@ QWindowsPipeReader::QWindowsPipeReader(QObject *parent)
lastError(ERROR_SUCCESS), lastError(ERROR_SUCCESS),
state(Stopped), state(Stopped),
readSequenceStarted(false), readSequenceStarted(false),
pipeBroken(false), pipeBroken(true),
readyReadPending(false), readyReadPending(false),
winEventActPosted(false) winEventActPosted(false)
{ {
@ -112,10 +112,6 @@ void QWindowsPipeReader::stop()
void QWindowsPipeReader::drainAndStop() void QWindowsPipeReader::drainAndStop()
{ {
cancelAsyncRead(Draining); cancelAsyncRead(Draining);
// Note that signals are not emitted in the call below, as the caller
// is expected to do that synchronously.
consumePending();
} }
/*! /*!
@ -123,10 +119,11 @@ void QWindowsPipeReader::drainAndStop()
*/ */
void QWindowsPipeReader::cancelAsyncRead(State newState) void QWindowsPipeReader::cancelAsyncRead(State newState)
{ {
pipeBroken = true;
if (state != Running) if (state != Running)
return; return;
QMutexLocker locker(&mutex); mutex.lock();
state = newState; state = newState;
if (readSequenceStarted) { if (readSequenceStarted) {
// This can legitimately fail due to the GetOverlappedResult() // This can legitimately fail due to the GetOverlappedResult()
@ -142,11 +139,17 @@ void QWindowsPipeReader::cancelAsyncRead(State newState)
// Wait for callback to complete. // Wait for callback to complete.
do { do {
locker.unlock(); mutex.unlock();
waitForNotification(); waitForNotification();
locker.relock(); mutex.lock();
} while (readSequenceStarted); } while (readSequenceStarted);
} }
mutex.unlock();
// Because pipeBroken was set earlier, finish reading to keep the class
// state consistent. Note that signals are not emitted in the call
// below, as the caller is expected to do that synchronously.
consumePending();
} }
/*! /*!

View File

@ -147,7 +147,6 @@ private:
Q_PRIVATE_SLOT(d_func(), void _q_stateChanged(QAbstractSocket::SocketState)) Q_PRIVATE_SLOT(d_func(), void _q_stateChanged(QAbstractSocket::SocketState))
Q_PRIVATE_SLOT(d_func(), void _q_errorOccurred(QAbstractSocket::SocketError)) Q_PRIVATE_SLOT(d_func(), void _q_errorOccurred(QAbstractSocket::SocketError))
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
Q_PRIVATE_SLOT(d_func(), void _q_canWrite())
Q_PRIVATE_SLOT(d_func(), void _q_pipeClosed()) Q_PRIVATE_SLOT(d_func(), void _q_pipeClosed())
Q_PRIVATE_SLOT(d_func(), void _q_winError(ulong, const QString &)) Q_PRIVATE_SLOT(d_func(), void _q_winError(ulong, const QString &))
#else #else

View File

@ -136,7 +136,7 @@ public:
qint64 pipeWriterBytesToWrite() const; qint64 pipeWriterBytesToWrite() const;
void _q_canRead(); void _q_canRead();
void _q_bytesWritten(qint64 bytes); void _q_bytesWritten(qint64 bytes);
void _q_canWrite(); void writeToSocket();
void _q_pipeClosed(); void _q_pipeClosed();
void _q_winError(ulong windowsError, const QString &function); void _q_winError(ulong windowsError, const QString &function);
HANDLE handle; HANDLE handle;

View File

@ -282,7 +282,7 @@ qint64 QLocalSocket::writeData(const char *data, qint64 len)
QObjectPrivate::connect(d->pipeWriter, &QWindowsPipeWriter::bytesWritten, QObjectPrivate::connect(d->pipeWriter, &QWindowsPipeWriter::bytesWritten,
d, &QLocalSocketPrivate::_q_bytesWritten); d, &QLocalSocketPrivate::_q_bytesWritten);
} }
d->_q_canWrite(); d->writeToSocket();
return len; return len;
} }
@ -434,21 +434,22 @@ void QLocalSocketPrivate::_q_bytesWritten(qint64 bytes)
QScopedValueRollback<bool> guard(emittedBytesWritten, true); QScopedValueRollback<bool> guard(emittedBytesWritten, true);
emit q->bytesWritten(bytes); emit q->bytesWritten(bytes);
} }
_q_canWrite();
}
void QLocalSocketPrivate::_q_canWrite()
{
Q_Q(QLocalSocket);
if (writeBuffer.isEmpty()) { if (writeBuffer.isEmpty()) {
if (state == QLocalSocket::ClosingState && pipeWriterBytesToWrite() == 0) if (state == QLocalSocket::ClosingState && pipeWriterBytesToWrite() == 0)
q->close(); q->close();
} else { } else {
writeToSocket();
}
}
void QLocalSocketPrivate::writeToSocket()
{
Q_ASSERT(pipeWriter); Q_ASSERT(pipeWriter);
Q_ASSERT(!writeBuffer.isEmpty());
if (!pipeWriter->isWriteOperationActive()) if (!pipeWriter->isWriteOperationActive())
pipeWriter->write(writeBuffer.read()); pipeWriter->write(writeBuffer.read());
} }
}
qintptr QLocalSocket::socketDescriptor() const qintptr QLocalSocket::socketDescriptor() const
{ {
@ -488,7 +489,8 @@ bool QLocalSocket::waitForDisconnected(int msecs)
QDeadlineTimer deadline(msecs); QDeadlineTimer deadline(msecs);
while (!d->pipeReader->isPipeClosed()) { while (!d->pipeReader->isPipeClosed()) {
d->_q_canWrite(); if (!d->writeBuffer.isEmpty())
d->writeToSocket();
QSocketPoller poller(*d); QSocketPoller poller(*d);
if (!poller.poll(deadline)) if (!poller.poll(deadline))
@ -499,7 +501,7 @@ bool QLocalSocket::waitForDisconnected(int msecs)
// When the read buffer is full, the read sequence is not running, // When the read buffer is full, the read sequence is not running,
// so we need to peek the pipe to detect disconnection. // so we need to peek the pipe to detect disconnection.
if (poller.waitForClose) if (poller.waitForClose && isValid())
d->pipeReader->checkPipeState(); d->pipeReader->checkPipeState();
d->pipeReader->checkForReadyRead(); d->pipeReader->checkForReadyRead();
@ -523,7 +525,8 @@ bool QLocalSocket::waitForReadyRead(int msecs)
QDeadlineTimer deadline(msecs); QDeadlineTimer deadline(msecs);
while (!d->pipeReader->isPipeClosed()) { while (!d->pipeReader->isPipeClosed()) {
d->_q_canWrite(); if (!d->writeBuffer.isEmpty())
d->writeToSocket();
QSocketPoller poller(*d); QSocketPoller poller(*d);
if (poller.waitForClose || !poller.poll(deadline)) if (poller.waitForClose || !poller.poll(deadline))
@ -548,9 +551,11 @@ bool QLocalSocket::waitForBytesWritten(int msecs)
QDeadlineTimer deadline(msecs); QDeadlineTimer deadline(msecs);
while (!d->pipeReader->isPipeClosed()) { while (!d->pipeReader->isPipeClosed()) {
if (bytesToWrite() == 0) if (!d->writeBuffer.isEmpty()) {
d->writeToSocket();
} else if (d->pipeWriterBytesToWrite() == 0) {
return false; return false;
d->_q_canWrite(); }
QSocketPoller poller(*d); QSocketPoller poller(*d);
if (!poller.poll(deadline)) if (!poller.poll(deadline))