[qt/qt/qtgrpc]: Summary of bulk changes made

KDE Git Services - Bulk Change <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git repository change summary for qt/qt/qtgrpc
Pushed by mirror-service into branch 'dev'.
Changed from ec01274cf9b9b4dcc42527210efd178fef2021b8 to 0427da31b4fccfc6889cd96f3beda98fdf1a2cce
Acknowledgement was received that this change introduces only existing code that has been pushed to another public open source repository.

This change contains the following new commits:

Git commit 99c2d5308c476dde5848d02b4aa21f610338bf68 by Dennis Oberst on 21/07/2026 at 18:17..
test(end2end/interceptors): fail by throwing instead of aborting

The test redefined QTEST_FAIL_ACTION to call std::abort() so that the
QTest macros could be used inside non-void helpers and callbacks. Any
failing check therefore killed the binary: SIGABRT locally, exit code
0xC0000409 on Windows. The XML log was truncated mid-run, so
qt-testrunner reported "the test executable probably crashed" instead
of the actual failure and could not re-run the failed function.

Define QTEST_THROW_ON_FAIL/QTEST_THROW_ON_SKIP instead. The macros then
expand to a throw expression, which compiles in non-void contexts; the
testlib harness catches the exception, records the failure, and the
remaining tests keep running and produce a parseable XML log. Checks
running on the mock server's completion-queue thread keep their fatal
behavior, as the exception terminates the process there, like abort()
did.

Pick-to: 6.12 6.11
Change-Id: Id565991f6c545016d606e762430ade55b252d5ee
Reviewed-by: Ivan Solovev <[email protected]>
https://invent.kde.org/qt/qt/qtgrpc/-/commit/99c2d5308c476dde5848d02b4aa21f610338bf68

Git commit 378ea257f8ced5e52430d6e587025205f20e4240 by Dennis Oberst on 21/07/2026 at 18:18..
QGrpcHttp2Channel: drain the outgoing queue iteratively

Http2Handler::processQueue() dequeued a single message and sent it,
relying on the uploadFinished handler to call processQueue() again for
the next one. On a fast link sendDATA() completes synchronously and
emits uploadFinished from within the call, so a large backlog drained
through one deep recursion (processQueue -> sendDATA -> uploadFinished
-> processQueue -> ...), one stack frame per queued message.
Burst-writing more than ~10k small messages overflowed the stack and
crashed the client with SIGSEGV.

Flatten the drain into a loop guarded against re-entrancy: a re-entrant
processQueue() call flags and returns, while the active loop dequeues
the next message. This resolves the recursion.

[ChangeLog][QtGrpc][QGrpcHttp2Channel] Fixed a stack overflow that could
crash the client when burst-writing a very large number of messages on a
client-streaming or bidirectional RPC.

Fixes: QTBUG-148062
Task-number: QTBUG-147934
Pick-to: 6.12 6.11 6.8
Change-Id: I058a9e3428ea94aad663fdf392949624702f750a
Reviewed-by: Ivan Solovev <[email protected]>
https://invent.kde.org/qt/qt/qtgrpc/-/commit/378ea257f8ced5e52430d6e587025205f20e4240

Git commit 73e54ca3dc29465412d23ec80d5473d7010c4276 by Dennis Oberst on 21/07/2026 at 18:18..
QGrpcHttp2Channel: validate server-sent grpc-status range

The parsed value was cast directly to StatusCode without a range check.
A hostile server could send e.g. "99" or "-1", narrowing into an
undefined StatusCode enumerator that application switch statements may
branch on unpredictably.

Reject any value outside [0, Unauthenticated] with StatusCode::Unknown
instead, mirroring upstream gRPC's own GRPC_STATUS_UNKNOWN fallback for
invalid status codes.

Pick-to: 6.12 6.11 6.8
Fixes: QTBUG-148148
Change-Id: Ib251e39459cd44786e9264e2b4e6657ae8b6128e
Reviewed-by: Ivan Solovev <[email protected]>
https://invent.kde.org/qt/qt/qtgrpc/-/commit/73e54ca3dc29465412d23ec80d5473d7010c4276

Git commit ae0cd2f5e8d06e56940c94fcce4c0ca83cfbcbd6 by Dennis Oberst on 21/07/2026 at 18:18..
QGrpcHttp2Channel: sanitize server-controlled bytes in warning logs

Three qCWarning calls printed server-controlled header keys and values
via printf-style %s, letting a hostile server inject newlines, NUL
bytes, or other control characters into the log stream (CWE-117).

Wrap each server-supplied operand in sanitizedForLog(), which delegates
to QtDebugUtils::toPrintable() to escape non-printable bytes and bound
the logged output at 256 bytes, preventing log flooding.

As a drive-by, fix the "unexcpected" typo in the grpc-reserved-header
messages.

Pick-to: 6.12 6.11 6.8
Fixes: QTBUG-148149
Change-Id: Icbd4a73a3ffb0890a4b9abe4144ecca47d87c873
Reviewed-by: Ivan Solovev <[email protected]>
https://invent.kde.org/qt/qt/qtgrpc/-/commit/ae0cd2f5e8d06e56940c94fcce4c0ca83cfbcbd6

Git commit 497afb0eaeae3445f542f7aed4fc5d792c602bb4 by Dennis Oberst on 21/07/2026 at 18:18..
QGrpcHttp2Channel: finish and cancel active streams on destruction

Destroying a channel with active streams never cancelled them on the
wire. The QHttp2Connection is parented to the transport socket, so the
implicitly defaulted destructor of QGrpcHttp2ChannelPrivate destroyed
it from inside the socket's QObject destructor. At that point the
socket's derived type is already gone and QHttp2Stream::~QHttp2Stream
skips its RST_STREAM frame (getSocket() resolves to nullptr), so the
peer cannot distinguish a deliberate cancellation from a connection
loss.

Delete the connection explicitly while the socket is still intact, and
flush the socket afterwards, since the socket destructor discards any
unsent data. Disconnect the socket's signals first so no error handler
runs on a channel that is mid-destruction. Also reset m_connection
before deleting it in handleSocketError() so a reentrant socket error
cannot double-delete it.

The client side was equally silent: pending operations never received
finished() and appeared to hang forever. Emit finish with Unavailable
for every active handler before tearing down the transport, so callers
observe the channel's destruction.

The regression test drives a minimal HTTP/2 server that acknowledges
the client preface, waits for the request HEADERS, and asserts that an
RST_STREAM frame arrives when the channel is destroyed with the call
still in flight, and that the call locally finishes with Unavailable.

Pick-to: 6.12 6.11 6.8
Change-Id: Ia1e87d57857d48cd15b87e2c3b71e50b419d6d45
Reviewed-by: Ivan Solovev <[email protected]>
https://invent.kde.org/qt/qt/qtgrpc/-/commit/497afb0eaeae3445f542f7aed4fc5d792c602bb4

Git commit 9a33b4eb625ffb42d57c6d3046da12a570ede6d7 by Dennis Oberst on 21/07/2026 at 18:18..
test(end2end): move QTest::failOnWarning into init()

It had no real effect in initTestCase.

Pick-to: 6.12
Change-Id: I52d5ed0cc06ecda75413b8cf24b37e8b0d079c36
Reviewed-by: Ivan Solovev <[email protected]>
https://invent.kde.org/qt/qt/qtgrpc/-/commit/9a33b4eb625ffb42d57c6d3046da12a570ede6d7

Git commit c3f2aaa4854a1b5ff11c064c79f3f0bc0efac8ba by Dennis Oberst on 21/07/2026 at 18:18..
QGrpcHttp2Channel: fix maximumReceiveMessageSize(0) semantics

Amends 8c8f0e3cffa4003cd549a0a6761297f53e1a5f75.

A limit of 0 was silently expanded to the transport cap, making it
effectively unlimited. The value is now used verbatim: 0 accepts only
zero-length messages, giving callers a consistent "value is the limit"
contract and removing a hidden special case.

Also unify the environment variable handling for other env-vars that
will be introduced after this commit, and picked back manually into
older branches. This commit is the first of its kind, so we do it here
already:

 * Introduced 'macro.h2EnvFallback' to unify the environment fallbacks
 * readEnvUnsignedInt(const char *name)
 * doc section: 'Environment variable fallbacks'

Found before the QtGRPC API review.

Task-number: QTBUG-145258
Task-number: QTBUG-145399
Fixes: QTBUG-146592
Pick-to: 6.12
Change-Id: I0fc2780b6d6ddbffed2466cfe8d92d1c0324b2c5
Reviewed-by: Ivan Solovev <[email protected]>
https://invent.kde.org/qt/qt/qtgrpc/-/commit/c3f2aaa4854a1b5ff11c064c79f3f0bc0efac8ba

Git commit 7fe45ea97d20932057ecee7b0d2337934d38f136 by Dennis Oberst on 21/07/2026 at 18:19..
mockserver: stack-allocate TagProcessor in tests

Tests created their TagProcessor via MockServer::createProcessor(),
which returned a unique_ptr. Lambdas captured that unique_ptr by
reference and called processor.get() when they fired later.

libc++'s std::unique_ptr::reset() nulls the stored pointer before it
invokes the deleter. So while ~TagProcessor() blocked in
waitForTagCompletion(5s), the TagProcessor object itself was alive (that
is the whole reason it waits, since Finish completions are expected to
arrive after the client has already finished) yet processor.get() read
through the unique_ptr already returned nullptr. Any callback that
constructed a new tag in that window forwarded the null into
AbstractTag and crashed on processor->registerTag(this).

The previous workaround captured processor.get() up front at a single
call site. Every test in this file (and in the interceptors test)
followed the same [&] / processor.get() pattern, so the possibility for
a crash existed at every call site. This flaky behavior got observed in
recent CI runs.

Drop MockServer::createProcessor() and stack-allocate TagProcessor:

    TagProcessor processor(m_server.get());

The address of a stack object is stable for its entire lifetime,
including while its destructor runs, so &processor in a callback is
always a valid TagProcessor*. There is no smart-pointer indirection
left to lie about the pointee.

Pick-to: 6.12
Change-Id: Iebec19bce57879778b2b5a5c0e94ce00ca62945d
Reviewed-by: Ivan Solovev <[email protected]>
https://invent.kde.org/qt/qt/qtgrpc/-/commit/7fe45ea97d20932057ecee7b0d2337934d38f136

Git commit 0427da31b4fccfc6889cd96f3beda98fdf1a2cce by Dennis Oberst on 21/07/2026 at 18:19..
QGrpcHttp2Channel: raise the default HTTP/2 receive window

A bare QHttp2Configuration advertises only a 64 KiB receive window for
both the stream and the connection. HTTP/2 flow control bounds the
unacknowledged data the peer may send, so download throughput is capped
at window/RTT: on a fast link the sender stalls waiting for
WINDOW_UPDATE frames and the pipe sits idle.

Default to a 4 MiB per-stream and 16 MiB per-connection receive window.
The connection window is kept the larger of the two so it never
bottlenecks multiplexed streams; both match the windows used by the C++
gRPC reference and Go's net/http2.

Measured with tests/manual/grpc/benchmarks (asyncbenchserver, 1000
calls, http loopback, arm64 macOS), stock vs. patched:

payload  RPC              before    after      delta
16 KiB   ServerStreaming  428 MB/s  1.10 GB/s  +157%
256 KiB  ServerStreaming  470 MB/s  2.68 GB/s  +471%
1 MiB    ServerStreaming  471 MB/s  2.98 GB/s  +533%  (2.23 s → 353 ms)
1 MiB    BidiStreaming    743 MB/s  2.31 GB/s  +211%  (2.82 s → 910 ms)
1 MiB    UnaryCall        747 MB/s  1.89 GB/s  +153%  (2.81 s → 1.11 s)

UnaryCall server→client response latency at 1 MiB drops from 2.19 ms to
454 µs (−79%). Small messages stay within run-to-run noise, and
upload-only ClientStreaming is unchanged because the client receive
window does not govern outbound data.

[ChangeLog][QtGrpc][QGrpcHttp2Channel] The default HTTP/2 receive window
was raised from 64 KiB to 4 MiB per stream and 16 MiB per connection.
The per-stream window can be tuned via the
QT_GRPC_HTTP2_STREAM_RECEIVE_WINDOW_SIZE environment variable and the
connection window via QT_GRPC_HTTP2_CONNECTION_RECEIVE_WINDOW_SIZE

Pick-to: 6.12 6.11 6.8
Fixes: QTBUG-136766
Change-Id: Ide6ae9dd941283519052632db7165583d1002a00
Reviewed-by: Ivan Solovev <[email protected]>
https://invent.kde.org/qt/qt/qtgrpc/-/commit/0427da31b4fccfc6889cd96f3beda98fdf1a2cce
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.