winrt: Do not lose initial data for TCP connections

When a client connects and sends data immediately it was possible that
initial data was lost as the state was set too late. If the callback was
called before the state was set the socket engine just discarded the
data. So the state has to be set before the callback is registered.

The new implementation needs a list of pending read operations. It can
happen that the "readyRead" callback is triggered directly while
"put_Completed" is called. The callback reassigns readOp which causes a
"function not implemented" exception when it jumps back to the
"put_Completed" call in "initialize"

Task-number: QTBUG-55889
Change-Id: I5f52e3377b6176f1f90f227ac0bf52b60ee2d95a
Reviewed-by: Maurice Kalinowski <maurice.kalinowski@qt.io>
This commit is contained in:
Oliver Wolff 2016-10-10 14:35:19 +02:00
parent d8c72f4154
commit dcf7da7c93
2 changed files with 27 additions and 9 deletions

View File

@ -317,26 +317,31 @@ bool QNativeSocketEngine::initialize(qintptr socketDescriptor, QAbstractSocket::
// Start processing incoming data
if (d->socketType == QAbstractSocket::TcpSocket) {
HRESULT hr;
QEventDispatcherWinRT::runOnXamlThread([d, &hr, socket, this]() {
QEventDispatcherWinRT::runOnXamlThread([&hr, socket, socketState, this]() {
Q_D(QNativeSocketEngine);
ComPtr<IBuffer> buffer;
HRESULT hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer);
RETURN_OK_IF_FAILED("initialize(): Could not create buffer");
ComPtr<IInputStream> stream;
hr = socket->get_InputStream(&stream);
RETURN_OK_IF_FAILED("initialize(): Could not obtain input stream");
hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, d->readOp.GetAddressOf());
ComPtr<IAsyncBufferOperation> readOp;
hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, readOp.GetAddressOf());
RETURN_OK_IF_FAILED_WITH_ARGS("initialize(): Failed to read from the socket buffer (%s).",
socketDescription(this).constData());
hr = d->readOp->put_Completed(Callback<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
d->pendingReadOps.append(readOp);
d->socketState = socketState;
hr = readOp->put_Completed(Callback<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
RETURN_OK_IF_FAILED_WITH_ARGS("initialize(): Failed to set socket read callback (%s).",
socketDescription(this).constData());
return S_OK;
});
if (FAILED(hr))
return false;
} else {
d->socketState = socketState;
}
d->socketState = socketState;
return true;
}
@ -567,9 +572,9 @@ void QNativeSocketEngine::close()
}
#endif // _MSC_VER >= 1900
if (d->readOp) {
for (ComPtr<IAsyncBufferOperation> readOp : d->pendingReadOps) {
ComPtr<IAsyncInfo> info;
hr = d->readOp.As(&info);
hr = readOp.As(&info);
Q_ASSERT_SUCCEEDED(hr);
if (info) {
hr = info->Cancel();
@ -933,9 +938,11 @@ void QNativeSocketEngine::establishRead()
hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer);
RETURN_HR_IF_FAILED("establishRead(): Failed to create buffer");
hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, &d->readOp);
ComPtr<IAsyncBufferOperation> readOp;
hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, readOp.GetAddressOf());
RETURN_HR_IF_FAILED("establishRead(): Failed to initiate socket read");
hr = d->readOp->put_Completed(Callback<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
d->pendingReadOps.append(readOp);
hr = readOp->put_Completed(Callback<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
RETURN_HR_IF_FAILED("establishRead(): Failed to register read callback");
return S_OK;
});
@ -1410,7 +1417,15 @@ HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncBufferOperation *async
}
Q_Q(QNativeSocketEngine);
for (int i = 0; i < pendingReadOps.count(); ++i) {
if (pendingReadOps.at(i).Get() == asyncInfo) {
pendingReadOps.takeAt(i);
break;
}
}
static QMutex mutex;
mutex.lock();
// A read in UnconnectedState will close the socket and return -1 and thus tell the caller,
// that the connection was closed. The socket cannot be closed here, as the subsequent read
// might fail then.
@ -1463,6 +1478,7 @@ HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncBufferOperation *async
if (notifyOnRead)
emit q->readReady();
mutex.unlock();
hr = QEventDispatcherWinRT::runOnXamlThread([buffer, q, this]() {
UINT32 readBufferLength;
@ -1476,12 +1492,14 @@ HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncBufferOperation *async
hr = buffer->put_Length(0);
RETURN_HR_IF_FAILED("handleReadyRead(): Could not set buffer length");
ComPtr<IAsyncBufferOperation> readOp;
hr = stream->ReadAsync(buffer.Get(), readBufferLength, InputStreamOptions_Partial, &readOp);
if (FAILED(hr)) {
qErrnoWarning(hr, "handleReadyRead(): Could not read into socket stream buffer (%s).",
socketDescription(q).constData());
return S_OK;
}
pendingReadOps.append(readOp);
hr = readOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &QNativeSocketEnginePrivate::handleReadyRead).Get());
if (FAILED(hr)) {
qErrnoWarning(hr, "handleReadyRead(): Failed to set socket read callback (%s).",

View File

@ -214,7 +214,7 @@ private:
{ return reinterpret_cast<ABI::Windows::Networking::Sockets::IDatagramSocket *>(socketDescriptor); }
Microsoft::WRL::ComPtr<ABI::Windows::Networking::Sockets::IStreamSocketListener> tcpListener;
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> connectOp;
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer *, UINT32>> readOp;
QVector<Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer *, UINT32>>> pendingReadOps;
QBuffer readBytes;
QMutex readMutex;
bool emitOnNewDatagram;