winrt: Cancel pending read operations before tcp sockets are closed

According to the documentation tcp sockets are closed properly if
their instances are deleted when no read operation is pending. Thus we
have to keep track of the running read operation, cancel it (only
available on Windows 10) and delete it before closing the socket.

As there is no way to cancel the read operation on Windows 8.1 the
hard reset still happens there.

Change-Id: Idc75178f7d05057b610ac7000e95486d6a52cb85
Reviewed-by: Samuel Nevala <samuel.nevala@intopalo.com>
Reviewed-by: Maurice Kalinowski <maurice.kalinowski@theqtcompany.com>
This commit is contained in:
Oliver Wolff 2016-04-11 13:28:09 +02:00
parent d8667fde18
commit 1e229c7041
2 changed files with 78 additions and 48 deletions

View File

@ -395,28 +395,30 @@ int QNativeSocketEngine::accept()
IStreamSocket *socket = d->pendingConnections.takeFirst();
HRESULT hr;
ComPtr<IBuffer> buffer;
hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IInputStream> stream;
hr = socket->get_InputStream(&stream);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IAsyncBufferOperation> op;
hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, &op);
if (FAILED(hr)) {
qErrnoWarning(hr, "accept(): Failed to read from the socket buffer (%s).",
socketDescription(this).constData());
return -1;
}
hr = QEventDispatcherWinRT::runOnXamlThread([d, op]() {
return op->put_Completed(Callback<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
hr = QEventDispatcherWinRT::runOnXamlThread([d, socket, this]() {
ComPtr<IBuffer> buffer;
HRESULT hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer);
RETURN_HR_IF_FAILED("accept(): Could not create buffer");
ComPtr<IInputStream> stream;
hr = socket->get_InputStream(&stream);
RETURN_HR_IF_FAILED("accept(): Could not obtain input stream");
hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, &d->readOp);
if (FAILED(hr)) {
qErrnoWarning(hr, "accept(): Failed to read from the socket buffer (%s).",
socketDescription(this).constData());
return E_FAIL;
}
hr = d->readOp->put_Completed(Callback<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
if (FAILED(hr)) {
qErrnoWarning(hr, "accept(): Failed to set socket read callback (%s).",
socketDescription(this).constData());
return E_FAIL;
}
return S_OK;
});
if (FAILED(hr)) {
qErrnoWarning(hr, "accept(): Failed to set socket read callback (%s).",
socketDescription(this).constData());
if (hr == E_FAIL)
return -1;
}
Q_ASSERT_SUCCEEDED(hr);
d->currentConnections.append(socket);
SocketHandler *handler = gSocketHandler();
@ -454,6 +456,32 @@ void QNativeSocketEngine::close()
}
}
#if _MSC_VER >= 1900
// To close the connection properly (not with a hard reset) all pending read operation have to
// be finished or cancelled. The API isn't available on Windows 8.1 though.
ComPtr<IStreamSocket3> socket3;
hr = d->tcpSocket()->QueryInterface(IID_PPV_ARGS(&socket3));
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IAsyncAction> action;
hr = socket3->CancelIOAsync(&action);
Q_ASSERT_SUCCEEDED(hr);
hr = QWinRTFunctions::await(action);
Q_ASSERT_SUCCEEDED(hr);
#endif // _MSC_VER >= 1900
if (d->readOp) {
ComPtr<IAsyncInfo> info;
hr = d->readOp.As(&info);
Q_ASSERT_SUCCEEDED(hr);
if (info) {
hr = info->Cancel();
Q_ASSERT_SUCCEEDED(hr);
hr = info->Close();
Q_ASSERT_SUCCEEDED(hr);
}
}
if (d->socketDescriptor != -1) {
ComPtr<IClosable> socket;
if (d->socketType == QAbstractSocket::TcpSocket) {
@ -784,17 +812,16 @@ void QNativeSocketEngine::establishRead()
hr = QEventDispatcherWinRT::runOnXamlThread([d]() {
ComPtr<IInputStream> stream;
HRESULT hr = d->tcpSocket()->get_InputStream(&stream);
RETURN_HR_IF_FAILED("QNativeSocketEngine::establishRead: Failed to get socket input stream");
RETURN_HR_IF_FAILED("establishRead(): Failed to get socket input stream");
ComPtr<IBuffer> buffer;
hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer);
RETURN_HR_IF_FAILED("QNativeSocketEngine::establishRead: Failed to create buffer");
RETURN_HR_IF_FAILED("establishRead(): Failed to create buffer");
ComPtr<IAsyncBufferOperation> op;
hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, &op);
RETURN_HR_IF_FAILED("QNativeSocketEngine::establishRead: Failed to initiate socket read");
hr = op->put_Completed(Callback<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
RETURN_HR_IF_FAILED("QNativeSocketEngine::establishRead: Failed to register read callback");
hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, &d->readOp);
RETURN_HR_IF_FAILED("establishRead(): Failed to initiate socket read");
hr = d->readOp->put_Completed(Callback<SocketReadCompletedHandler>(d, &QNativeSocketEnginePrivate::handleReadyRead).Get());
RETURN_HR_IF_FAILED("establishRead(): Failed to register read callback");
return S_OK;
});
Q_ASSERT_SUCCEEDED(hr);
@ -1294,31 +1321,33 @@ HRESULT QNativeSocketEnginePrivate::handleReadyRead(IAsyncBufferOperation *async
if (notifyOnRead)
emit q->readReady();
ComPtr<IInputStream> stream;
hr = tcpSocket()->get_InputStream(&stream);
Q_ASSERT_SUCCEEDED(hr);
hr = QEventDispatcherWinRT::runOnXamlThread([buffer, q, this]() {
UINT32 readBufferLength;
ComPtr<IInputStream> stream;
HRESULT hr = tcpSocket()->get_InputStream(&stream);
RETURN_HR_IF_FAILED("handleReadyRead(): Could not obtain input stream");
// Reuse the stream buffer
hr = buffer->get_Capacity(&bufferLength);
Q_ASSERT_SUCCEEDED(hr);
hr = buffer->put_Length(0);
Q_ASSERT_SUCCEEDED(hr);
// Reuse the stream buffer
hr = buffer->get_Capacity(&readBufferLength);
RETURN_HR_IF_FAILED("handleReadyRead(): Could not obtain buffer capacity");
hr = buffer->put_Length(0);
RETURN_HR_IF_FAILED("handleReadyRead(): Could not set buffer length");
ComPtr<IAsyncBufferOperation> op;
hr = stream->ReadAsync(buffer.Get(), bufferLength, InputStreamOptions_Partial, &op);
if (FAILED(hr)) {
qErrnoWarning(hr, "handleReadyRead(): Could not read into socket stream buffer (%s).",
socketDescription(q).constData());
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;
}
hr = readOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &QNativeSocketEnginePrivate::handleReadyRead).Get());
if (FAILED(hr)) {
qErrnoWarning(hr, "handleReadyRead(): Failed to set socket read callback (%s).",
socketDescription(q).constData());
return S_OK;
}
return S_OK;
}
hr = QEventDispatcherWinRT::runOnXamlThread([op, this]() {
return op->put_Completed(Callback<SocketReadCompletedHandler>(this, &QNativeSocketEnginePrivate::handleReadyRead).Get());
});
if (FAILED(hr)) {
qErrnoWarning(hr, "handleReadyRead(): Failed to set socket read callback (%s).",
socketDescription(q).constData());
return S_OK;
}
Q_ASSERT_SUCCEEDED(hr);
return S_OK;
}

View File

@ -199,6 +199,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;
QBuffer readBytes;
QMutex readMutex;