1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
From 9fc3cd03da14b8a9a47fa2e8aaf324c220a3a230 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= <marten.nordheim@qt.io>
Date: Thu, 5 Jun 2025 12:12:56 +0200
Subject: [PATCH 25/31] Http2: Explicitly send RST_STREAM on cancelled request
It will do this when it gets deleted, but due to deleteLater just adding
an event to the event queue the events that are ahead in the queue may
use the stream in question. This would lead to a variant of
'stream not found', or specifically in the case of the bugreport, a
'HEADERS on non-existent stream' stream error.
Amends 6b4e11e63ead46dde5c1002c123ca964bb6aa342
Fixes: QTBUG-137427
Change-Id: I5f2b2d5660866f1ad12aaafbb4e572b08ed5a6e4
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
(cherry picked from commit 904aec2f372e2981af19bf762583a0ef42ec6bb9)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit a0ab39b24b763d8d44badf2512a859e3d6351cc3)
---
src/network/access/qhttp2protocolhandler.cpp | 1 +
tests/auto/network/access/http2/tst_http2.cpp | 64 +++++++++++++++++++
2 files changed, 65 insertions(+)
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
index 84bf5b1a78d..a4bf29815f7 100644
--- a/src/network/access/qhttp2protocolhandler.cpp
+++ b/src/network/access/qhttp2protocolhandler.cpp
@@ -265,6 +265,7 @@ bool QHttp2ProtocolHandler::tryRemoveReply(QHttpNetworkReply *reply)
{
QHttp2Stream *stream = streamIDs.take(reply);
if (stream) {
+ stream->sendRST_STREAM(stream->isUploadingDATA() ? Http2::CANCEL : Http2::HTTP2_NO_ERROR);
requestReplyPairs.remove(stream);
stream->deleteLater();
return true;
diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp
index 12f9cba6c79..d255629441b 100644
--- a/tests/auto/network/access/http2/tst_http2.cpp
+++ b/tests/auto/network/access/http2/tst_http2.cpp
@@ -88,6 +88,7 @@ private slots:
void goaway();
void earlyResponse();
void earlyError();
+ void abortReply();
void connectToHost_data();
void connectToHost();
void maxFrameSize();
@@ -774,6 +775,69 @@ void tst_Http2::earlyError()
QTRY_VERIFY(serverGotSettingsACK);
}
+/*
+ As above this test relies a bit on timing so we are
+ using QHttpNetworkRequest directly.
+*/
+void tst_Http2::abortReply()
+{
+ clearHTTP2State();
+ serverPort = 0;
+
+ const auto serverConnectionType = defaultConnectionType() == H2Type::h2c ? H2Type::h2Direct
+ : H2Type::h2Alpn;
+ ServerPtr targetServer(newServer(defaultServerSettings, serverConnectionType));
+
+ QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ nRequests = 1;
+
+ // SETUP create QHttpNetworkConnection primed for http2 usage
+ const auto connectionType = serverConnectionType == H2Type::h2Direct
+ ? QHttpNetworkConnection::ConnectionTypeHTTP2Direct
+ : QHttpNetworkConnection::ConnectionTypeHTTP2;
+ QHttpNetworkConnection connection(1, "127.0.0.1", serverPort, true, false, nullptr,
+ connectionType);
+ QSslConfiguration config = QSslConfiguration::defaultConfiguration();
+ config.setAllowedNextProtocols({"h2"});
+ connection.setSslConfiguration(config);
+ connection.ignoreSslErrors();
+
+ // SETUP manually setup the QHttpNetworkRequest
+ QHttpNetworkRequest req;
+ req.setSsl(true);
+ req.setHTTP2Allowed(true);
+ if (defaultConnectionType() == H2Type::h2c)
+ req.setH2cAllowed(true);
+ req.setOperation(QHttpNetworkRequest::Post);
+ req.setUrl(requestUrl(defaultConnectionType()));
+ // ^ All the above is set-up, the real code starts below v
+
+ std::unique_ptr<QHttpNetworkReply> reply{connection.sendRequest(req)};
+ QVERIFY(reply);
+ QSemaphore sem;
+ QObject::connect(reply.get(), &QHttpNetworkReply::requestSent, reply.get(), [&](){
+ reply.reset();
+ sem.release();
+ });
+
+ // failOnWarning doesn't work for qCritical, so we set this env-var:
+ const char envvar[] = "QT_FATAL_CRITICALS";
+ auto restore = qScopeGuard([envvar, prev = qgetenv(envvar)]() {
+ qputenv(envvar, prev);
+ });
+ qputenv(envvar, "1");
+ QTest::failOnWarning(QRegularExpression("HEADERS on invalid stream"));
+ QVERIFY(QTest::qWaitFor([&sem]() { return sem.tryAcquire(); }));
+ using namespace std::chrono_literals;
+ // Process some extra events in case they trigger an error:
+ QTest::qWait(100ms);
+}
+
+
void tst_Http2::connectToHost_data()
{
// The attribute to set on a new request:
--
2.50.1
|