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>
This commit is contained in:
parent
915be6606e
commit
5a233f7556
@ -1,6 +1,6 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2020 The Qt Company Ltd.
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the QtCore module of the Qt Toolkit.
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
@ -41,75 +41,61 @@
|
|||||||
#include "qiodevice_p.h"
|
#include "qiodevice_p.h"
|
||||||
#include <qelapsedtimer.h>
|
#include <qelapsedtimer.h>
|
||||||
#include <qscopedvaluerollback.h>
|
#include <qscopedvaluerollback.h>
|
||||||
#include <qcoreapplication.h>
|
|
||||||
#include <QMutexLocker>
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
QWindowsPipeReader::Overlapped::Overlapped(QWindowsPipeReader *reader)
|
||||||
|
: pipeReader(reader)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWindowsPipeReader::Overlapped::clear()
|
||||||
|
{
|
||||||
|
ZeroMemory(this, sizeof(OVERLAPPED));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QWindowsPipeReader::QWindowsPipeReader(QObject *parent)
|
QWindowsPipeReader::QWindowsPipeReader(QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
handle(INVALID_HANDLE_VALUE),
|
handle(INVALID_HANDLE_VALUE),
|
||||||
eventHandle(CreateEvent(NULL, FALSE, FALSE, NULL)),
|
overlapped(this),
|
||||||
syncHandle(CreateEvent(NULL, TRUE, FALSE, NULL)),
|
|
||||||
waitObject(NULL),
|
|
||||||
readBufferMaxSize(0),
|
readBufferMaxSize(0),
|
||||||
actualReadBufferSize(0),
|
actualReadBufferSize(0),
|
||||||
pendingReadBytes(0),
|
|
||||||
lastError(ERROR_SUCCESS),
|
|
||||||
stopped(true),
|
stopped(true),
|
||||||
readSequenceStarted(false),
|
readSequenceStarted(false),
|
||||||
|
notifiedCalled(false),
|
||||||
pipeBroken(false),
|
pipeBroken(false),
|
||||||
readyReadPending(false),
|
readyReadPending(false),
|
||||||
winEventActPosted(false),
|
|
||||||
inReadyRead(false)
|
inReadyRead(false)
|
||||||
{
|
{
|
||||||
ZeroMemory(&overlapped, sizeof(OVERLAPPED));
|
connect(this, &QWindowsPipeReader::_q_queueReadyRead,
|
||||||
overlapped.hEvent = eventHandle;
|
this, &QWindowsPipeReader::emitPendingReadyRead, Qt::QueuedConnection);
|
||||||
waitObject = CreateThreadpoolWait(waitCallback, this, NULL);
|
|
||||||
if (waitObject == NULL)
|
|
||||||
qErrnoWarning("QWindowsPipeReader: CreateThreadpollWait failed.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QWindowsPipeReader::~QWindowsPipeReader()
|
QWindowsPipeReader::~QWindowsPipeReader()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
CloseThreadpoolWait(waitObject);
|
|
||||||
CloseHandle(eventHandle);
|
|
||||||
CloseHandle(syncHandle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\internal
|
|
||||||
Sets the handle to read from. The handle must be valid.
|
Sets the handle to read from. The handle must be valid.
|
||||||
Do not call this function if the pipe is running.
|
|
||||||
*/
|
*/
|
||||||
void QWindowsPipeReader::setHandle(HANDLE hPipeReadEnd)
|
void QWindowsPipeReader::setHandle(HANDLE hPipeReadEnd)
|
||||||
{
|
{
|
||||||
readBuffer.clear();
|
readBuffer.clear();
|
||||||
actualReadBufferSize = 0;
|
actualReadBufferSize = 0;
|
||||||
readyReadPending = false;
|
|
||||||
pendingReadBytes = 0;
|
|
||||||
handle = hPipeReadEnd;
|
handle = hPipeReadEnd;
|
||||||
pipeBroken = false;
|
pipeBroken = false;
|
||||||
lastError = ERROR_SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\internal
|
|
||||||
Stops the asynchronous read sequence.
|
Stops the asynchronous read sequence.
|
||||||
If the read sequence is running then the I/O operation is canceled.
|
If the read sequence is running then the I/O operation is canceled.
|
||||||
*/
|
*/
|
||||||
void QWindowsPipeReader::stop()
|
void QWindowsPipeReader::stop()
|
||||||
{
|
{
|
||||||
if (stopped)
|
|
||||||
return;
|
|
||||||
|
|
||||||
mutex.lock();
|
|
||||||
stopped = true;
|
stopped = true;
|
||||||
if (readSequenceStarted) {
|
if (readSequenceStarted) {
|
||||||
// Trying to disable callback before canceling the operation.
|
|
||||||
// Callback invocation is unnecessary here.
|
|
||||||
SetThreadpoolWait(waitObject, NULL, NULL);
|
|
||||||
if (!CancelIoEx(handle, &overlapped)) {
|
if (!CancelIoEx(handle, &overlapped)) {
|
||||||
const DWORD dwError = GetLastError();
|
const DWORD dwError = GetLastError();
|
||||||
if (dwError != ERROR_NOT_FOUND) {
|
if (dwError != ERROR_NOT_FOUND) {
|
||||||
@ -117,33 +103,8 @@ void QWindowsPipeReader::stop()
|
|||||||
handle);
|
handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
readSequenceStarted = false;
|
waitForNotification(-1);
|
||||||
}
|
}
|
||||||
mutex.unlock();
|
|
||||||
|
|
||||||
WaitForThreadpoolWaitCallbacks(waitObject, TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Sets the size of internal read buffer.
|
|
||||||
*/
|
|
||||||
void QWindowsPipeReader::setMaxReadBufferSize(qint64 size)
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&mutex);
|
|
||||||
readBufferMaxSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Returns \c true if async operation is in progress, there is
|
|
||||||
pending data to read, or a read error is pending.
|
|
||||||
*/
|
|
||||||
bool QWindowsPipeReader::isReadOperationActive() const
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&mutex);
|
|
||||||
return readSequenceStarted || readyReadPending
|
|
||||||
|| (lastError != ERROR_SUCCESS && !pipeBroken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -162,7 +123,6 @@ qint64 QWindowsPipeReader::read(char *data, qint64 maxlen)
|
|||||||
if (pipeBroken && actualReadBufferSize == 0)
|
if (pipeBroken && actualReadBufferSize == 0)
|
||||||
return 0; // signal EOF
|
return 0; // signal EOF
|
||||||
|
|
||||||
mutex.lock();
|
|
||||||
qint64 readSoFar;
|
qint64 readSoFar;
|
||||||
// If startAsyncRead() has read data, copy it to its destination.
|
// If startAsyncRead() has read data, copy it to its destination.
|
||||||
if (maxlen == 1 && actualReadBufferSize > 0) {
|
if (maxlen == 1 && actualReadBufferSize > 0) {
|
||||||
@ -173,10 +133,9 @@ qint64 QWindowsPipeReader::read(char *data, qint64 maxlen)
|
|||||||
readSoFar = readBuffer.read(data, qMin(actualReadBufferSize, maxlen));
|
readSoFar = readBuffer.read(data, qMin(actualReadBufferSize, maxlen));
|
||||||
actualReadBufferSize -= readSoFar;
|
actualReadBufferSize -= readSoFar;
|
||||||
}
|
}
|
||||||
mutex.unlock();
|
|
||||||
|
|
||||||
if (!pipeBroken) {
|
if (!pipeBroken) {
|
||||||
if (!stopped)
|
if (!readSequenceStarted && !stopped)
|
||||||
startAsyncRead();
|
startAsyncRead();
|
||||||
if (readSoFar == 0)
|
if (readSoFar == 0)
|
||||||
return -2; // signal EWOULDBLOCK
|
return -2; // signal EWOULDBLOCK
|
||||||
@ -185,220 +144,131 @@ qint64 QWindowsPipeReader::read(char *data, qint64 maxlen)
|
|||||||
return readSoFar;
|
return readSoFar;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Returns \c true if a complete line of data can be read from the buffer.
|
|
||||||
*/
|
|
||||||
bool QWindowsPipeReader::canReadLine() const
|
bool QWindowsPipeReader::canReadLine() const
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&mutex);
|
|
||||||
return readBuffer.indexOf('\n', actualReadBufferSize) >= 0;
|
return readBuffer.indexOf('\n', actualReadBufferSize) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Starts an asynchronous read sequence on the pipe.
|
|
||||||
*/
|
|
||||||
void QWindowsPipeReader::startAsyncRead()
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&mutex);
|
|
||||||
|
|
||||||
if (readSequenceStarted || lastError != ERROR_SUCCESS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
stopped = false;
|
|
||||||
startAsyncReadLocked();
|
|
||||||
|
|
||||||
// Do not post the event, if the read operation will be completed asynchronously.
|
|
||||||
if (!readyReadPending && lastError == ERROR_SUCCESS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!winEventActPosted) {
|
|
||||||
winEventActPosted = true;
|
|
||||||
locker.unlock();
|
|
||||||
QCoreApplication::postEvent(this, new QEvent(QEvent::WinEventAct));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Starts a new read sequence. Thread-safety should be ensured by the caller.
|
|
||||||
*/
|
|
||||||
void QWindowsPipeReader::startAsyncReadLocked()
|
|
||||||
{
|
|
||||||
const DWORD minReadBufferSize = 4096;
|
|
||||||
forever {
|
|
||||||
qint64 bytesToRead = qMax(checkPipeState(), minReadBufferSize);
|
|
||||||
if (lastError != ERROR_SUCCESS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) {
|
|
||||||
bytesToRead = readBufferMaxSize - readBuffer.size();
|
|
||||||
if (bytesToRead <= 0) {
|
|
||||||
// Buffer is full. User must read data from the buffer
|
|
||||||
// before we can read more from the pipe.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *ptr = readBuffer.reserve(bytesToRead);
|
|
||||||
|
|
||||||
// ReadFile() returns true, if the read operation completes synchronously.
|
|
||||||
// We don't need to call GetOverlappedResult() additionally, because
|
|
||||||
// 'numberOfBytesRead' is valid in this case.
|
|
||||||
DWORD numberOfBytesRead;
|
|
||||||
if (!ReadFile(handle, ptr, bytesToRead, &numberOfBytesRead, &overlapped))
|
|
||||||
break;
|
|
||||||
|
|
||||||
readCompleted(ERROR_SUCCESS, numberOfBytesRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
const DWORD dwError = GetLastError();
|
|
||||||
if (dwError == ERROR_IO_PENDING) {
|
|
||||||
// Operation has been queued and will complete in the future.
|
|
||||||
readSequenceStarted = true;
|
|
||||||
SetThreadpoolWait(waitObject, eventHandle, NULL);
|
|
||||||
} else {
|
|
||||||
// Any other errors are treated as EOF.
|
|
||||||
readCompleted(dwError, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Thread pool callback procedure.
|
|
||||||
*/
|
|
||||||
void QWindowsPipeReader::waitCallback(PTP_CALLBACK_INSTANCE instance, PVOID context,
|
|
||||||
PTP_WAIT wait, TP_WAIT_RESULT waitResult)
|
|
||||||
{
|
|
||||||
Q_UNUSED(instance);
|
|
||||||
Q_UNUSED(wait);
|
|
||||||
Q_UNUSED(waitResult);
|
|
||||||
QWindowsPipeReader *pipeReader = reinterpret_cast<QWindowsPipeReader *>(context);
|
|
||||||
|
|
||||||
// Get the result of the asynchronous operation.
|
|
||||||
DWORD numberOfBytesTransfered = 0;
|
|
||||||
DWORD errorCode = ERROR_SUCCESS;
|
|
||||||
if (!GetOverlappedResult(pipeReader->handle, &pipeReader->overlapped,
|
|
||||||
&numberOfBytesTransfered, FALSE))
|
|
||||||
errorCode = GetLastError();
|
|
||||||
|
|
||||||
QMutexLocker locker(&pipeReader->mutex);
|
|
||||||
|
|
||||||
// After the reader 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 read sequence should
|
|
||||||
// be started in this case.
|
|
||||||
if (pipeReader->stopped)
|
|
||||||
return;
|
|
||||||
|
|
||||||
pipeReader->readSequenceStarted = false;
|
|
||||||
|
|
||||||
// Do not overwrite error code, if error has been detected by
|
|
||||||
// checkPipeState() in waitForPipeClosed().
|
|
||||||
if (pipeReader->lastError != ERROR_SUCCESS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
pipeReader->readCompleted(errorCode, numberOfBytesTransfered);
|
|
||||||
if (pipeReader->lastError == ERROR_SUCCESS)
|
|
||||||
pipeReader->startAsyncReadLocked();
|
|
||||||
|
|
||||||
if (!pipeReader->winEventActPosted) {
|
|
||||||
pipeReader->winEventActPosted = true;
|
|
||||||
locker.unlock();
|
|
||||||
QCoreApplication::postEvent(pipeReader, new QEvent(QEvent::WinEventAct));
|
|
||||||
} else {
|
|
||||||
locker.unlock();
|
|
||||||
}
|
|
||||||
SetEvent(pipeReader->syncHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\internal
|
\internal
|
||||||
Will be called whenever the read operation completes.
|
Will be called whenever the read operation completes.
|
||||||
*/
|
*/
|
||||||
void QWindowsPipeReader::readCompleted(DWORD errorCode, DWORD numberOfBytesRead)
|
void QWindowsPipeReader::notified(DWORD errorCode, DWORD numberOfBytesRead)
|
||||||
{
|
{
|
||||||
// ERROR_MORE_DATA is not an error. We're connected to a message mode
|
notifiedCalled = true;
|
||||||
// pipe and the message didn't fit into the pipe's system
|
readSequenceStarted = false;
|
||||||
// buffer. We will read the remaining data in the next call.
|
|
||||||
if (errorCode == ERROR_SUCCESS || errorCode == ERROR_MORE_DATA) {
|
|
||||||
readyReadPending = true;
|
|
||||||
pendingReadBytes += numberOfBytesRead;
|
|
||||||
readBuffer.truncate(actualReadBufferSize + pendingReadBytes);
|
|
||||||
} else {
|
|
||||||
lastError = errorCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
switch (errorCode) {
|
||||||
\internal
|
case ERROR_SUCCESS:
|
||||||
Receives notification that the read operation has completed.
|
break;
|
||||||
*/
|
case ERROR_MORE_DATA:
|
||||||
bool QWindowsPipeReader::event(QEvent *e)
|
// This is not an error. We're connected to a message mode
|
||||||
{
|
// pipe and the message didn't fit into the pipe's system
|
||||||
if (e->type() == QEvent::WinEventAct) {
|
// buffer. We will read the remaining data in the next call.
|
||||||
emitPendingSignals(true);
|
break;
|
||||||
return true;
|
case ERROR_BROKEN_PIPE:
|
||||||
}
|
case ERROR_PIPE_NOT_CONNECTED:
|
||||||
return QObject::event(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Emits pending signals in the main thread. Returns \c true,
|
|
||||||
if readyRead() was emitted.
|
|
||||||
*/
|
|
||||||
bool QWindowsPipeReader::emitPendingSignals(bool allowWinActPosting)
|
|
||||||
{
|
|
||||||
mutex.lock();
|
|
||||||
|
|
||||||
// Enable QEvent::WinEventAct posting.
|
|
||||||
if (allowWinActPosting)
|
|
||||||
winEventActPosted = false;
|
|
||||||
|
|
||||||
bool emitReadyRead = false;
|
|
||||||
if (readyReadPending) {
|
|
||||||
readyReadPending = false;
|
|
||||||
actualReadBufferSize += pendingReadBytes;
|
|
||||||
pendingReadBytes = 0;
|
|
||||||
emitReadyRead = true;
|
|
||||||
}
|
|
||||||
const DWORD dwError = lastError;
|
|
||||||
|
|
||||||
mutex.unlock();
|
|
||||||
|
|
||||||
// Disable any further processing, if the pipe was stopped.
|
|
||||||
if (stopped)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (emitReadyRead && !inReadyRead) {
|
|
||||||
QScopedValueRollback<bool> guard(inReadyRead, true);
|
|
||||||
emit readyRead();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trigger 'pipeBroken' only once.
|
|
||||||
if (dwError != ERROR_SUCCESS && !pipeBroken) {
|
|
||||||
pipeBroken = true;
|
pipeBroken = true;
|
||||||
if (dwError != ERROR_BROKEN_PIPE && dwError != ERROR_PIPE_NOT_CONNECTED)
|
break;
|
||||||
emit winError(dwError, QLatin1String("QWindowsPipeReader::emitPendingSignals"));
|
case ERROR_OPERATION_ABORTED:
|
||||||
emit pipeClosed();
|
if (stopped)
|
||||||
|
break;
|
||||||
|
Q_FALLTHROUGH();
|
||||||
|
default:
|
||||||
|
emit winError(errorCode, QLatin1String("QWindowsPipeReader::notified"));
|
||||||
|
pipeBroken = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return emitReadyRead;
|
// After the reader 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 read sequence should
|
||||||
|
// be started in this case.
|
||||||
|
if (stopped)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (pipeBroken) {
|
||||||
|
emit pipeClosed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actualReadBufferSize += numberOfBytesRead;
|
||||||
|
readBuffer.truncate(actualReadBufferSize);
|
||||||
|
startAsyncRead();
|
||||||
|
if (!readyReadPending) {
|
||||||
|
readyReadPending = true;
|
||||||
|
emit _q_queueReadyRead(QWindowsPipeReader::QPrivateSignal());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\internal
|
||||||
|
Reads data from the pipe into the readbuffer.
|
||||||
|
*/
|
||||||
|
void QWindowsPipeReader::startAsyncRead()
|
||||||
|
{
|
||||||
|
const DWORD minReadBufferSize = 4096;
|
||||||
|
qint64 bytesToRead = qMax(checkPipeState(), minReadBufferSize);
|
||||||
|
if (pipeBroken)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) {
|
||||||
|
bytesToRead = readBufferMaxSize - readBuffer.size();
|
||||||
|
if (bytesToRead <= 0) {
|
||||||
|
// Buffer is full. User must read data from the buffer
|
||||||
|
// before we can read more from the pipe.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ptr = readBuffer.reserve(bytesToRead);
|
||||||
|
|
||||||
|
stopped = false;
|
||||||
|
readSequenceStarted = true;
|
||||||
|
overlapped.clear();
|
||||||
|
if (!ReadFileEx(handle, ptr, bytesToRead, &overlapped, &readFileCompleted)) {
|
||||||
|
readSequenceStarted = false;
|
||||||
|
|
||||||
|
const DWORD dwError = GetLastError();
|
||||||
|
switch (dwError) {
|
||||||
|
case ERROR_BROKEN_PIPE:
|
||||||
|
case ERROR_PIPE_NOT_CONNECTED:
|
||||||
|
// It may happen, that the other side closes the connection directly
|
||||||
|
// after writing data. Then we must set the appropriate socket state.
|
||||||
|
pipeBroken = true;
|
||||||
|
emit pipeClosed();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
emit winError(dwError, QLatin1String("QWindowsPipeReader::startAsyncRead"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\internal
|
||||||
|
Called when ReadFileEx finished the read operation.
|
||||||
|
*/
|
||||||
|
void QWindowsPipeReader::readFileCompleted(DWORD errorCode, DWORD numberOfBytesTransfered,
|
||||||
|
OVERLAPPED *overlappedBase)
|
||||||
|
{
|
||||||
|
Overlapped *overlapped = static_cast<Overlapped *>(overlappedBase);
|
||||||
|
overlapped->pipeReader->notified(errorCode, numberOfBytesTransfered);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\internal
|
\internal
|
||||||
Returns the number of available bytes in the pipe.
|
Returns the number of available bytes in the pipe.
|
||||||
Sets QWindowsPipeReader::lastError if the connection is broken.
|
Sets QWindowsPipeReader::pipeBroken to true if the connection is broken.
|
||||||
*/
|
*/
|
||||||
DWORD QWindowsPipeReader::checkPipeState()
|
DWORD QWindowsPipeReader::checkPipeState()
|
||||||
{
|
{
|
||||||
DWORD bytes;
|
DWORD bytes;
|
||||||
if (PeekNamedPipe(handle, nullptr, 0, nullptr, &bytes, nullptr))
|
if (PeekNamedPipe(handle, nullptr, 0, nullptr, &bytes, nullptr))
|
||||||
return bytes;
|
return bytes;
|
||||||
|
if (!pipeBroken) {
|
||||||
readCompleted(GetLastError(), 0);
|
pipeBroken = true;
|
||||||
|
emit pipeClosed();
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,21 +276,27 @@ bool QWindowsPipeReader::waitForNotification(int timeout)
|
|||||||
{
|
{
|
||||||
QElapsedTimer t;
|
QElapsedTimer t;
|
||||||
t.start();
|
t.start();
|
||||||
|
notifiedCalled = false;
|
||||||
int msecs = timeout;
|
int msecs = timeout;
|
||||||
do {
|
while (SleepEx(msecs == -1 ? INFINITE : msecs, TRUE) == WAIT_IO_COMPLETION) {
|
||||||
DWORD waitRet = WaitForSingleObjectEx(syncHandle,
|
if (notifiedCalled)
|
||||||
msecs == -1 ? INFINITE : msecs, TRUE);
|
|
||||||
if (waitRet == WAIT_OBJECT_0)
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (waitRet != WAIT_IO_COMPLETION)
|
// Some other I/O completion routine was called. Wait some more.
|
||||||
return false;
|
|
||||||
|
|
||||||
// Some I/O completion routine was called. Wait some more.
|
|
||||||
msecs = qt_subtract_from_timeout(timeout, t.elapsed());
|
msecs = qt_subtract_from_timeout(timeout, t.elapsed());
|
||||||
} while (msecs != 0);
|
if (!msecs)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return notifiedCalled;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
void QWindowsPipeReader::emitPendingReadyRead()
|
||||||
|
{
|
||||||
|
if (readyReadPending) {
|
||||||
|
readyReadPending = false;
|
||||||
|
QScopedValueRollback<bool> guard(inReadyRead, true);
|
||||||
|
emit readyRead();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -430,21 +306,25 @@ bool QWindowsPipeReader::waitForNotification(int timeout)
|
|||||||
*/
|
*/
|
||||||
bool QWindowsPipeReader::waitForReadyRead(int msecs)
|
bool QWindowsPipeReader::waitForReadyRead(int msecs)
|
||||||
{
|
{
|
||||||
if (readBufferMaxSize && actualReadBufferSize >= readBufferMaxSize)
|
if (readyReadPending) {
|
||||||
return false;
|
if (!inReadyRead)
|
||||||
|
emitPendingReadyRead();
|
||||||
// Prepare handle for waiting.
|
|
||||||
ResetEvent(syncHandle);
|
|
||||||
|
|
||||||
// It is necessary to check if there is already data in the queue.
|
|
||||||
if (emitPendingSignals(false))
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure that 'syncHandle' was triggered by the thread pool callback.
|
if (!readSequenceStarted)
|
||||||
if (pipeBroken || !waitForNotification(msecs))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return emitPendingSignals(false);
|
if (!waitForNotification(msecs))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (readyReadPending) {
|
||||||
|
if (!inReadyRead)
|
||||||
|
emitPendingReadyRead();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -457,18 +337,9 @@ bool QWindowsPipeReader::waitForPipeClosed(int msecs)
|
|||||||
stopWatch.start();
|
stopWatch.start();
|
||||||
forever {
|
forever {
|
||||||
waitForReadyRead(0);
|
waitForReadyRead(0);
|
||||||
if (pipeBroken)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// When the read buffer is full, the read sequence is not running.
|
|
||||||
// So, we should peek the pipe to detect disconnect.
|
|
||||||
mutex.lock();
|
|
||||||
checkPipeState();
|
checkPipeState();
|
||||||
mutex.unlock();
|
|
||||||
emitPendingSignals(false);
|
|
||||||
if (pipeBroken)
|
if (pipeBroken)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (stopWatch.hasExpired(msecs - sleepTime))
|
if (stopWatch.hasExpired(msecs - sleepTime))
|
||||||
return false;
|
return false;
|
||||||
Sleep(sleepTime);
|
Sleep(sleepTime);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2020 The Qt Company Ltd.
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the QtCore module of the Qt Toolkit.
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
@ -52,7 +52,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qmutex.h>
|
|
||||||
#include <private/qringbuffer_p.h>
|
#include <private/qringbuffer_p.h>
|
||||||
|
|
||||||
#include <qt_windows.h>
|
#include <qt_windows.h>
|
||||||
@ -70,7 +69,7 @@ public:
|
|||||||
void startAsyncRead();
|
void startAsyncRead();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
void setMaxReadBufferSize(qint64 size);
|
void setMaxReadBufferSize(qint64 size) { readBufferMaxSize = size; }
|
||||||
qint64 maxReadBufferSize() const { return readBufferMaxSize; }
|
qint64 maxReadBufferSize() const { return readBufferMaxSize; }
|
||||||
|
|
||||||
bool isPipeClosed() const { return pipeBroken; }
|
bool isPipeClosed() const { return pipeBroken; }
|
||||||
@ -80,41 +79,41 @@ public:
|
|||||||
bool waitForReadyRead(int msecs);
|
bool waitForReadyRead(int msecs);
|
||||||
bool waitForPipeClosed(int msecs);
|
bool waitForPipeClosed(int msecs);
|
||||||
|
|
||||||
bool isReadOperationActive() const;
|
bool isReadOperationActive() const { return readSequenceStarted; }
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void winError(ulong, const QString &);
|
void winError(ulong, const QString &);
|
||||||
void readyRead();
|
void readyRead();
|
||||||
void pipeClosed();
|
void pipeClosed();
|
||||||
|
void _q_queueReadyRead(QPrivateSignal);
|
||||||
protected:
|
|
||||||
bool event(QEvent *e) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startAsyncReadLocked();
|
static void CALLBACK readFileCompleted(DWORD errorCode, DWORD numberOfBytesTransfered,
|
||||||
static void CALLBACK waitCallback(PTP_CALLBACK_INSTANCE instance, PVOID context,
|
OVERLAPPED *overlappedBase);
|
||||||
PTP_WAIT wait, TP_WAIT_RESULT waitResult);
|
void notified(DWORD errorCode, DWORD numberOfBytesRead);
|
||||||
void readCompleted(DWORD errorCode, DWORD numberOfBytesRead);
|
|
||||||
DWORD checkPipeState();
|
DWORD checkPipeState();
|
||||||
bool waitForNotification(int timeout);
|
bool waitForNotification(int timeout);
|
||||||
bool emitPendingSignals(bool allowWinActPosting);
|
void emitPendingReadyRead();
|
||||||
|
|
||||||
|
class Overlapped : public OVERLAPPED
|
||||||
|
{
|
||||||
|
Q_DISABLE_COPY_MOVE(Overlapped)
|
||||||
|
public:
|
||||||
|
explicit Overlapped(QWindowsPipeReader *reader);
|
||||||
|
void clear();
|
||||||
|
QWindowsPipeReader *pipeReader;
|
||||||
|
};
|
||||||
|
|
||||||
HANDLE handle;
|
HANDLE handle;
|
||||||
HANDLE eventHandle;
|
Overlapped overlapped;
|
||||||
HANDLE syncHandle;
|
|
||||||
PTP_WAIT waitObject;
|
|
||||||
OVERLAPPED overlapped;
|
|
||||||
qint64 readBufferMaxSize;
|
qint64 readBufferMaxSize;
|
||||||
QRingBuffer readBuffer;
|
QRingBuffer readBuffer;
|
||||||
qint64 actualReadBufferSize;
|
qint64 actualReadBufferSize;
|
||||||
qint64 pendingReadBytes;
|
|
||||||
mutable QMutex mutex;
|
|
||||||
DWORD lastError;
|
|
||||||
bool stopped;
|
bool stopped;
|
||||||
bool readSequenceStarted;
|
bool readSequenceStarted;
|
||||||
|
bool notifiedCalled;
|
||||||
bool pipeBroken;
|
bool pipeBroken;
|
||||||
bool readyReadPending;
|
bool readyReadPending;
|
||||||
bool winEventActPosted;
|
|
||||||
bool inReadyRead;
|
bool inReadyRead;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2020 The Qt Company Ltd.
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the QtCore module of the Qt Toolkit.
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
@ -40,55 +40,178 @@
|
|||||||
#include "qwindowspipewriter_p.h"
|
#include "qwindowspipewriter_p.h"
|
||||||
#include "qiodevice_p.h"
|
#include "qiodevice_p.h"
|
||||||
#include <qscopedvaluerollback.h>
|
#include <qscopedvaluerollback.h>
|
||||||
#include <qcoreapplication.h>
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
QWindowsPipeWriter::Overlapped::Overlapped(QWindowsPipeWriter *pipeWriter)
|
||||||
|
: pipeWriter(pipeWriter)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWindowsPipeWriter::Overlapped::clear()
|
||||||
|
{
|
||||||
|
ZeroMemory(this, sizeof(OVERLAPPED));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QWindowsPipeWriter::QWindowsPipeWriter(HANDLE pipeWriteEnd, QObject *parent)
|
QWindowsPipeWriter::QWindowsPipeWriter(HANDLE pipeWriteEnd, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
handle(pipeWriteEnd),
|
handle(pipeWriteEnd),
|
||||||
eventHandle(CreateEvent(NULL, FALSE, FALSE, NULL)),
|
overlapped(this),
|
||||||
syncHandle(CreateEvent(NULL, TRUE, FALSE, NULL)),
|
|
||||||
waitObject(NULL),
|
|
||||||
pendingBytesWrittenValue(0),
|
pendingBytesWrittenValue(0),
|
||||||
lastError(ERROR_SUCCESS),
|
|
||||||
stopped(true),
|
stopped(true),
|
||||||
writeSequenceStarted(false),
|
writeSequenceStarted(false),
|
||||||
|
notifiedCalled(false),
|
||||||
bytesWrittenPending(false),
|
bytesWrittenPending(false),
|
||||||
winEventActPosted(false),
|
|
||||||
inBytesWritten(false)
|
inBytesWritten(false)
|
||||||
{
|
{
|
||||||
ZeroMemory(&overlapped, sizeof(OVERLAPPED));
|
connect(this, &QWindowsPipeWriter::_q_queueBytesWritten,
|
||||||
overlapped.hEvent = eventHandle;
|
this, &QWindowsPipeWriter::emitPendingBytesWrittenValue, Qt::QueuedConnection);
|
||||||
waitObject = CreateThreadpoolWait(waitCallback, this, NULL);
|
|
||||||
if (waitObject == NULL)
|
|
||||||
qErrnoWarning("QWindowsPipeWriter: CreateThreadpollWait failed.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QWindowsPipeWriter::~QWindowsPipeWriter()
|
QWindowsPipeWriter::~QWindowsPipeWriter()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
CloseThreadpoolWait(waitObject);
|
}
|
||||||
CloseHandle(eventHandle);
|
|
||||||
CloseHandle(syncHandle);
|
bool QWindowsPipeWriter::waitForWrite(int msecs)
|
||||||
|
{
|
||||||
|
if (bytesWrittenPending) {
|
||||||
|
emitPendingBytesWrittenValue();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!writeSequenceStarted)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!waitForNotification(msecs))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (bytesWrittenPending) {
|
||||||
|
emitPendingBytesWrittenValue();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 QWindowsPipeWriter::bytesToWrite() const
|
||||||
|
{
|
||||||
|
return buffer.size() + pendingBytesWrittenValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWindowsPipeWriter::emitPendingBytesWrittenValue()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWindowsPipeWriter::writeFileCompleted(DWORD errorCode, DWORD numberOfBytesTransfered,
|
||||||
|
OVERLAPPED *overlappedBase)
|
||||||
|
{
|
||||||
|
Overlapped *overlapped = static_cast<Overlapped *>(overlappedBase);
|
||||||
|
overlapped->pipeWriter->notified(errorCode, numberOfBytesTransfered);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\internal
|
\internal
|
||||||
Stops the asynchronous write sequence.
|
Will be called whenever the write operation completes.
|
||||||
If the write sequence is running then the I/O operation is canceled.
|
|
||||||
*/
|
*/
|
||||||
void QWindowsPipeWriter::stop()
|
void QWindowsPipeWriter::notified(DWORD errorCode, DWORD numberOfBytesWritten)
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
if (stopped)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mutex.lock();
|
pendingBytesWrittenValue += qint64(numberOfBytesWritten);
|
||||||
|
if (!bytesWrittenPending) {
|
||||||
|
bytesWrittenPending = true;
|
||||||
|
emit _q_queueBytesWritten(QWindowsPipeWriter::QPrivateSignal());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QWindowsPipeWriter::waitForNotification(int timeout)
|
||||||
|
{
|
||||||
|
QElapsedTimer t;
|
||||||
|
t.start();
|
||||||
|
notifiedCalled = false;
|
||||||
|
int msecs = timeout;
|
||||||
|
while (SleepEx(msecs == -1 ? INFINITE : msecs, TRUE) == WAIT_IO_COMPLETION) {
|
||||||
|
if (notifiedCalled)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Some other I/O completion routine was called. Wait some more.
|
||||||
|
msecs = qt_subtract_from_timeout(timeout, t.elapsed());
|
||||||
|
if (!msecs)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return notifiedCalled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QWindowsPipeWriter::write(const QByteArray &ba)
|
||||||
|
{
|
||||||
|
if (writeSequenceStarted)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
overlapped.clear();
|
||||||
|
buffer = ba;
|
||||||
|
stopped = false;
|
||||||
|
writeSequenceStarted = true;
|
||||||
|
if (!WriteFileEx(handle, buffer.constData(), buffer.size(),
|
||||||
|
&overlapped, &writeFileCompleted)) {
|
||||||
|
writeSequenceStarted = false;
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
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 true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QWindowsPipeWriter::stop()
|
||||||
|
{
|
||||||
stopped = true;
|
stopped = true;
|
||||||
|
bytesWrittenPending = false;
|
||||||
|
pendingBytesWrittenValue = 0;
|
||||||
if (writeSequenceStarted) {
|
if (writeSequenceStarted) {
|
||||||
// Trying to disable callback before canceling the operation.
|
|
||||||
// Callback invocation is unnecessary here.
|
|
||||||
SetThreadpoolWait(waitObject, NULL, NULL);
|
|
||||||
if (!CancelIoEx(handle, &overlapped)) {
|
if (!CancelIoEx(handle, &overlapped)) {
|
||||||
const DWORD dwError = GetLastError();
|
const DWORD dwError = GetLastError();
|
||||||
if (dwError != ERROR_NOT_FOUND) {
|
if (dwError != ERROR_NOT_FOUND) {
|
||||||
@ -96,250 +219,8 @@ void QWindowsPipeWriter::stop()
|
|||||||
handle);
|
handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeSequenceStarted = false;
|
waitForNotification(-1);
|
||||||
}
|
}
|
||||||
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 (lastError != ERROR_SUCCESS)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
writeBuffer.append(ba);
|
|
||||||
if (writeSequenceStarted)
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Thread pool callback procedure.
|
|
||||||
*/
|
|
||||||
void QWindowsPipeWriter::waitCallback(PTP_CALLBACK_INSTANCE instance, PVOID context,
|
|
||||||
PTP_WAIT wait, TP_WAIT_RESULT waitResult)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Will be called whenever the write operation completes.
|
|
||||||
*/
|
|
||||||
void QWindowsPipeWriter::writeCompleted(DWORD errorCode, DWORD numberOfBytesWritten)
|
|
||||||
{
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Receives notification that the write operation has completed.
|
|
||||||
*/
|
|
||||||
bool QWindowsPipeWriter::event(QEvent *e)
|
|
||||||
{
|
|
||||||
if (e->type() == QEvent::WinEventAct) {
|
|
||||||
emitPendingSignals(true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return QObject::event(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\internal
|
|
||||||
Emits pending signals in the main thread. Returns \c true,
|
|
||||||
if bytesWritten() was emitted.
|
|
||||||
*/
|
|
||||||
bool QWindowsPipeWriter::emitPendingSignals(bool allowWinActPosting)
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Disable any further processing, if the pipe was stopped.
|
|
||||||
if (stopped)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
emit canWrite();
|
|
||||||
if (!inBytesWritten) {
|
|
||||||
QScopedValueRollback<bool> guard(inBytesWritten, true);
|
|
||||||
emit bytesWritten(numberOfBytesWritten);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QWindowsPipeWriter::waitForNotification(int timeout)
|
|
||||||
{
|
|
||||||
QElapsedTimer t;
|
|
||||||
t.start();
|
|
||||||
int msecs = timeout;
|
|
||||||
do {
|
|
||||||
DWORD waitRet = WaitForSingleObjectEx(syncHandle,
|
|
||||||
msecs == -1 ? INFINITE : msecs, 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.
|
|
||||||
msecs = qt_subtract_from_timeout(timeout, t.elapsed());
|
|
||||||
} while (msecs != 0);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\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)
|
|
||||||
{
|
|
||||||
// Prepare handle for waiting.
|
|
||||||
ResetEvent(syncHandle);
|
|
||||||
|
|
||||||
// It is necessary to check if there is already pending signal.
|
|
||||||
if (emitPendingSignals(false))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Make sure that 'syncHandle' was triggered by the thread pool callback.
|
|
||||||
if (!isWriteOperationActive() || !waitForNotification(msecs))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return emitPendingSignals(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2020 The Qt Company Ltd.
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the QtCore module of the Qt Toolkit.
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
@ -54,9 +54,7 @@
|
|||||||
#include <QtCore/private/qglobal_p.h>
|
#include <QtCore/private/qglobal_p.h>
|
||||||
#include <qelapsedtimer.h>
|
#include <qelapsedtimer.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qmutex.h>
|
#include <qbytearray.h>
|
||||||
#include <private/qringbuffer_p.h>
|
|
||||||
|
|
||||||
#include <qt_windows.h>
|
#include <qt_windows.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
@ -119,37 +117,39 @@ public:
|
|||||||
bool write(const QByteArray &ba);
|
bool write(const QByteArray &ba);
|
||||||
void stop();
|
void stop();
|
||||||
bool waitForWrite(int msecs);
|
bool waitForWrite(int msecs);
|
||||||
bool isWriteOperationActive() const;
|
bool isWriteOperationActive() const { return writeSequenceStarted; }
|
||||||
qint64 bytesToWrite() const;
|
qint64 bytesToWrite() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void canWrite();
|
void canWrite();
|
||||||
void bytesWritten(qint64 bytes);
|
void bytesWritten(qint64 bytes);
|
||||||
|
void _q_queueBytesWritten(QPrivateSignal);
|
||||||
protected:
|
|
||||||
bool event(QEvent *e) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startAsyncWriteLocked();
|
static void CALLBACK writeFileCompleted(DWORD errorCode, DWORD numberOfBytesTransfered,
|
||||||
static void CALLBACK waitCallback(PTP_CALLBACK_INSTANCE instance, PVOID context,
|
OVERLAPPED *overlappedBase);
|
||||||
PTP_WAIT wait, TP_WAIT_RESULT waitResult);
|
void notified(DWORD errorCode, DWORD numberOfBytesWritten);
|
||||||
void writeCompleted(DWORD errorCode, DWORD numberOfBytesWritten);
|
|
||||||
bool waitForNotification(int timeout);
|
bool waitForNotification(int timeout);
|
||||||
bool emitPendingSignals(bool allowWinActPosting);
|
void emitPendingBytesWrittenValue();
|
||||||
|
|
||||||
|
class Overlapped : public OVERLAPPED
|
||||||
|
{
|
||||||
|
Q_DISABLE_COPY_MOVE(Overlapped)
|
||||||
|
public:
|
||||||
|
explicit Overlapped(QWindowsPipeWriter *pipeWriter);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
QWindowsPipeWriter *pipeWriter;
|
||||||
|
};
|
||||||
|
|
||||||
HANDLE handle;
|
HANDLE handle;
|
||||||
HANDLE eventHandle;
|
Overlapped overlapped;
|
||||||
HANDLE syncHandle;
|
QByteArray buffer;
|
||||||
PTP_WAIT waitObject;
|
|
||||||
OVERLAPPED overlapped;
|
|
||||||
QRingBuffer writeBuffer;
|
|
||||||
qint64 pendingBytesWrittenValue;
|
qint64 pendingBytesWrittenValue;
|
||||||
mutable QMutex mutex;
|
|
||||||
DWORD lastError;
|
|
||||||
bool stopped;
|
bool stopped;
|
||||||
bool writeSequenceStarted;
|
bool writeSequenceStarted;
|
||||||
|
bool notifiedCalled;
|
||||||
bool bytesWrittenPending;
|
bool bytesWrittenPending;
|
||||||
bool winEventActPosted;
|
|
||||||
bool inBytesWritten;
|
bool inBytesWritten;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,8 +35,6 @@
|
|||||||
#include <QtGui/qrasterwindow.h>
|
#include <QtGui/qrasterwindow.h>
|
||||||
#include <QtNetwork/qtcpserver.h>
|
#include <QtNetwork/qtcpserver.h>
|
||||||
#include <QtNetwork/qtcpsocket.h>
|
#include <QtNetwork/qtcpsocket.h>
|
||||||
#include <QtNetwork/qlocalserver.h>
|
|
||||||
#include <QtNetwork/qlocalsocket.h>
|
|
||||||
#include <QtCore/qelapsedtimer.h>
|
#include <QtCore/qelapsedtimer.h>
|
||||||
#include <QtCore/qtimer.h>
|
#include <QtCore/qtimer.h>
|
||||||
#include <QtCore/qwineventnotifier.h>
|
#include <QtCore/qwineventnotifier.h>
|
||||||
@ -53,7 +51,6 @@ class tst_NoQtEventLoop : public QObject
|
|||||||
private slots:
|
private slots:
|
||||||
void consumeMouseEvents();
|
void consumeMouseEvents();
|
||||||
void consumeSocketEvents();
|
void consumeSocketEvents();
|
||||||
void consumeLocalSocketEvents();
|
|
||||||
void consumeWinEvents_data();
|
void consumeWinEvents_data();
|
||||||
void consumeWinEvents();
|
void consumeWinEvents();
|
||||||
void deliverEventsInLivelock();
|
void deliverEventsInLivelock();
|
||||||
@ -321,44 +318,6 @@ void tst_NoQtEventLoop::consumeSocketEvents()
|
|||||||
QVERIFY(server.hasPendingConnections());
|
QVERIFY(server.hasPendingConnections());
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_NoQtEventLoop::consumeLocalSocketEvents()
|
|
||||||
{
|
|
||||||
int argc = 1;
|
|
||||||
char *argv[] = { const_cast<char *>("test"), 0 };
|
|
||||||
QGuiApplication app(argc, argv);
|
|
||||||
QLocalServer server;
|
|
||||||
QLocalSocket client;
|
|
||||||
QSignalSpy readyReadSpy(&client, &QIODevice::readyRead);
|
|
||||||
|
|
||||||
QVERIFY(server.listen("consumeLocalSocketEvents"));
|
|
||||||
client.connectToServer("consumeLocalSocketEvents");
|
|
||||||
QVERIFY(client.waitForConnected(200));
|
|
||||||
QVERIFY(server.waitForNewConnection(200));
|
|
||||||
QLocalSocket *clientSocket = server.nextPendingConnection();
|
|
||||||
QVERIFY(clientSocket);
|
|
||||||
QSignalSpy bytesWrittenSpy(clientSocket, &QIODevice::bytesWritten);
|
|
||||||
server.close();
|
|
||||||
|
|
||||||
bool timeExpired = false;
|
|
||||||
QTimer::singleShot(3000, Qt::CoarseTimer, [&timeExpired]() {
|
|
||||||
timeExpired = true;
|
|
||||||
});
|
|
||||||
QVERIFY(clientSocket->putChar(0));
|
|
||||||
|
|
||||||
// Exec own message loop
|
|
||||||
MSG msg;
|
|
||||||
while (::GetMessage(&msg, NULL, 0, 0)) {
|
|
||||||
::TranslateMessage(&msg);
|
|
||||||
::DispatchMessage(&msg);
|
|
||||||
|
|
||||||
if (timeExpired || readyReadSpy.count() != 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
QVERIFY(!timeExpired);
|
|
||||||
QCOMPARE(bytesWrittenSpy.count(), 1);
|
|
||||||
QCOMPARE(readyReadSpy.count(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tst_NoQtEventLoop::consumeWinEvents_data()
|
void tst_NoQtEventLoop::consumeWinEvents_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<bool>("peeking");
|
QTest::addColumn<bool>("peeking");
|
||||||
|
@ -634,12 +634,11 @@ void tst_QLocalSocket::readBufferOverflow()
|
|||||||
QCOMPARE(client.bytesAvailable(), 0);
|
QCOMPARE(client.bytesAvailable(), 0);
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
// ensure the previous write operation is finished
|
|
||||||
QVERIFY(serverSocket->waitForBytesWritten());
|
|
||||||
|
|
||||||
serverSocket->write(buffer, readBufferSize);
|
serverSocket->write(buffer, readBufferSize);
|
||||||
QVERIFY(serverSocket->waitForBytesWritten());
|
QVERIFY(serverSocket->waitForBytesWritten());
|
||||||
|
|
||||||
|
// ensure the read completion routine is called
|
||||||
|
SleepEx(100, true);
|
||||||
QVERIFY(client.waitForReadyRead());
|
QVERIFY(client.waitForReadyRead());
|
||||||
QCOMPARE(client.read(buffer, readBufferSize), qint64(readBufferSize));
|
QCOMPARE(client.read(buffer, readBufferSize), qint64(readBufferSize));
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# Generated from socket.pro.
|
# Generated from socket.pro.
|
||||||
|
|
||||||
add_subdirectory(qlocalsocket)
|
|
||||||
add_subdirectory(qtcpserver)
|
add_subdirectory(qtcpserver)
|
||||||
add_subdirectory(qudpsocket)
|
add_subdirectory(qudpsocket)
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
# Generated from qlocalsocket.pro.
|
|
||||||
|
|
||||||
#####################################################################
|
|
||||||
## tst_bench_qlocalsocket Binary:
|
|
||||||
#####################################################################
|
|
||||||
|
|
||||||
qt_internal_add_benchmark(tst_bench_qlocalsocket
|
|
||||||
SOURCES
|
|
||||||
tst_qlocalsocket.cpp
|
|
||||||
PUBLIC_LIBRARIES
|
|
||||||
Qt::Network
|
|
||||||
Qt::Test
|
|
||||||
)
|
|
||||||
|
|
||||||
#### Keys ignored in scope 1:.:.:qlocalsocket.pro:<TRUE>:
|
|
||||||
# TEMPLATE = "app"
|
|
@ -1,8 +0,0 @@
|
|||||||
TEMPLATE = app
|
|
||||||
TARGET = tst_bench_qlocalsocket
|
|
||||||
|
|
||||||
QT = network testlib
|
|
||||||
|
|
||||||
CONFIG += release
|
|
||||||
|
|
||||||
SOURCES += tst_qlocalsocket.cpp
|
|
@ -1,224 +0,0 @@
|
|||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2020 The Qt Company Ltd.
|
|
||||||
** Contact: https://www.qt.io/licensing/
|
|
||||||
**
|
|
||||||
** This file is part of the test suite of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
|
||||||
** 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 General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU
|
|
||||||
** General Public License version 3 as published by the Free Software
|
|
||||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
||||||
** 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-3.0.html.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
|
||||||
#include <QtCore/qglobal.h>
|
|
||||||
#include <QtCore/qthread.h>
|
|
||||||
#include <QtCore/qsemaphore.h>
|
|
||||||
#include <QtCore/qbytearray.h>
|
|
||||||
#include <QtCore/qeventloop.h>
|
|
||||||
#include <QtCore/qvector.h>
|
|
||||||
#include <QtCore/qelapsedtimer.h>
|
|
||||||
#include <QtNetwork/qlocalsocket.h>
|
|
||||||
#include <QtNetwork/qlocalserver.h>
|
|
||||||
|
|
||||||
class tst_QLocalSocket : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void pingPong_data();
|
|
||||||
void pingPong();
|
|
||||||
void dataExchange_data();
|
|
||||||
void dataExchange();
|
|
||||||
};
|
|
||||||
|
|
||||||
class ServerThread : public QThread
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
QSemaphore running;
|
|
||||||
|
|
||||||
explicit ServerThread(int chunkSize)
|
|
||||||
{
|
|
||||||
buffer.resize(chunkSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
QLocalServer server;
|
|
||||||
|
|
||||||
connect(&server, &QLocalServer::newConnection, [this, &server]() {
|
|
||||||
auto socket = server.nextPendingConnection();
|
|
||||||
|
|
||||||
connect(socket, &QLocalSocket::readyRead, [this, socket]() {
|
|
||||||
const qint64 bytesAvailable = socket->bytesAvailable();
|
|
||||||
Q_ASSERT(bytesAvailable <= this->buffer.size());
|
|
||||||
|
|
||||||
QCOMPARE(socket->read(this->buffer.data(), bytesAvailable), bytesAvailable);
|
|
||||||
QCOMPARE(socket->write(this->buffer.data(), bytesAvailable), bytesAvailable);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
QVERIFY(server.listen("foo"));
|
|
||||||
running.release();
|
|
||||||
exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QByteArray buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SocketFactory : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool stopped = false;
|
|
||||||
|
|
||||||
explicit SocketFactory(int chunkSize, int connections)
|
|
||||||
{
|
|
||||||
buffer.resize(chunkSize);
|
|
||||||
for (int i = 0; i < connections; ++i) {
|
|
||||||
QLocalSocket *socket = new QLocalSocket(this);
|
|
||||||
Q_CHECK_PTR(socket);
|
|
||||||
|
|
||||||
connect(this, &SocketFactory::start, [this, socket]() {
|
|
||||||
QCOMPARE(socket->write(this->buffer), this->buffer.size());
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(socket, &QLocalSocket::readyRead, [i, this, socket]() {
|
|
||||||
const qint64 bytesAvailable = socket->bytesAvailable();
|
|
||||||
Q_ASSERT(bytesAvailable <= this->buffer.size());
|
|
||||||
|
|
||||||
QCOMPARE(socket->read(this->buffer.data(), bytesAvailable), bytesAvailable);
|
|
||||||
emit this->bytesReceived(i, bytesAvailable);
|
|
||||||
|
|
||||||
if (!this->stopped)
|
|
||||||
QCOMPARE(socket->write(this->buffer.data(), bytesAvailable), bytesAvailable);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket->connectToServer("foo");
|
|
||||||
QCOMPARE(socket->state(), QLocalSocket::ConnectedState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void start();
|
|
||||||
void bytesReceived(int channel, qint64 bytes);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QByteArray buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
void tst_QLocalSocket::pingPong_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<int>("connections");
|
|
||||||
for (int value : {10, 50, 100, 1000, 5000})
|
|
||||||
QTest::addRow("connections: %d", value) << value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tst_QLocalSocket::pingPong()
|
|
||||||
{
|
|
||||||
QFETCH(int, connections);
|
|
||||||
|
|
||||||
const int iterations = 100000;
|
|
||||||
Q_ASSERT(iterations >= connections && connections > 0);
|
|
||||||
|
|
||||||
ServerThread serverThread(1);
|
|
||||||
serverThread.start();
|
|
||||||
// Wait for server to start.
|
|
||||||
QVERIFY(serverThread.running.tryAcquire(1, 3000));
|
|
||||||
|
|
||||||
SocketFactory factory(1, connections);
|
|
||||||
QEventLoop eventLoop;
|
|
||||||
QVector<qint64> bytesToRead;
|
|
||||||
QElapsedTimer timer;
|
|
||||||
|
|
||||||
bytesToRead.fill(iterations / connections, connections);
|
|
||||||
connect(&factory, &SocketFactory::bytesReceived,
|
|
||||||
[&bytesToRead, &connections, &factory, &eventLoop](int channel, qint64 bytes) {
|
|
||||||
Q_UNUSED(bytes);
|
|
||||||
|
|
||||||
if (--bytesToRead[channel] == 0 && --connections == 0) {
|
|
||||||
factory.stopped = true;
|
|
||||||
eventLoop.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
timer.start();
|
|
||||||
emit factory.start();
|
|
||||||
eventLoop.exec();
|
|
||||||
|
|
||||||
qDebug("Elapsed time: %.1f s", timer.elapsed() / 1000.0);
|
|
||||||
serverThread.quit();
|
|
||||||
serverThread.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tst_QLocalSocket::dataExchange_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<int>("connections");
|
|
||||||
QTest::addColumn<int>("chunkSize");
|
|
||||||
for (int connections : {1, 5, 10}) {
|
|
||||||
for (int chunkSize : {100, 1000, 10000, 100000}) {
|
|
||||||
QTest::addRow("connections: %d, chunk size: %d",
|
|
||||||
connections, chunkSize) << connections << chunkSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void tst_QLocalSocket::dataExchange()
|
|
||||||
{
|
|
||||||
QFETCH(int, connections);
|
|
||||||
QFETCH(int, chunkSize);
|
|
||||||
|
|
||||||
Q_ASSERT(chunkSize > 0 && connections > 0);
|
|
||||||
const qint64 timeToTest = 5000;
|
|
||||||
|
|
||||||
ServerThread serverThread(chunkSize);
|
|
||||||
serverThread.start();
|
|
||||||
// Wait for server to start.
|
|
||||||
QVERIFY(serverThread.running.tryAcquire(1, 3000));
|
|
||||||
|
|
||||||
SocketFactory factory(chunkSize, connections);
|
|
||||||
QEventLoop eventLoop;
|
|
||||||
qint64 totalReceived = 0;
|
|
||||||
QElapsedTimer timer;
|
|
||||||
|
|
||||||
connect(&factory, &SocketFactory::bytesReceived,
|
|
||||||
[&totalReceived, &timer, timeToTest, &factory, &eventLoop](int channel, qint64 bytes) {
|
|
||||||
Q_UNUSED(channel);
|
|
||||||
|
|
||||||
totalReceived += bytes;
|
|
||||||
if (timer.elapsed() >= timeToTest) {
|
|
||||||
factory.stopped = true;
|
|
||||||
eventLoop.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
timer.start();
|
|
||||||
emit factory.start();
|
|
||||||
eventLoop.exec();
|
|
||||||
|
|
||||||
qDebug("Transfer rate: %.1f MB/s", totalReceived / 1048.576 / timer.elapsed());
|
|
||||||
serverThread.quit();
|
|
||||||
serverThread.wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
QTEST_MAIN(tst_QLocalSocket)
|
|
||||||
|
|
||||||
#include "tst_qlocalsocket.moc"
|
|
@ -1,5 +1,4 @@
|
|||||||
TEMPLATE = subdirs
|
TEMPLATE = subdirs
|
||||||
SUBDIRS = \
|
SUBDIRS = \
|
||||||
qlocalsocket \
|
|
||||||
qtcpserver \
|
qtcpserver \
|
||||||
qudpsocket
|
qudpsocket
|
||||||
|
Loading…
x
Reference in New Issue
Block a user