From c2a3aeaa8c86b4eb309862376653f1c1b6231d78 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sun, 5 Jul 2026 09:24:14 +0200 Subject: [PATCH 1/2] quic: fix stall datagrams, if no pending streams If you are only sending datagrams and so streams, your datagrams could stall. There were two reasons: i. A SendPendingDataScope in SendDatagrams was missing. ii. SendPendingData did not attempt to send datagrams, if there were no stream data. Signed-off-by: Marten Richter --- src/quic/application.cc | 2 +- src/quic/session.cc | 1 + test/parallel/test-quic-session-preferred-address-ipv6.mjs | 3 +++ test/parallel/test-quic-session-preferred-address.mjs | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/quic/application.cc b/src/quic/application.cc index ce5d5e12154d8a..c57ff91d043c57 100644 --- a/src/quic/application.cc +++ b/src/quic/application.cc @@ -540,7 +540,7 @@ void Session::Application::SendPendingData() { // We call Application::ResumeStream directly (not Session::ResumeStream) // to avoid creating a SendPendingDataScope — we're already inside // SendPendingData and re-entering would just hit nwrite=0 again. - if (nwrite == 0) { + if (nwrite == 0 && (stream_data.id >= 0 || !session_->HasPendingDatagrams())) { Debug(session_, "Congestion or not our turn to send"); if (stream_data.id >= 0 && (stream_data.count > 0 || stream_data.fin)) { ResumeStream(stream_data.id); diff --git a/src/quic/session.cc b/src/quic/session.cc index 8380e477c01e80..c39eccf6e47307 100644 --- a/src/quic/session.cc +++ b/src/quic/session.cc @@ -2593,6 +2593,7 @@ datagram_id Session::SendDatagram(Store&& data) { return did; } } + Session::SendPendingDataScope send_scope(this); // Queue the datagram. It will be serialized into packets by // SendPendingData alongside stream data. diff --git a/test/parallel/test-quic-session-preferred-address-ipv6.mjs b/test/parallel/test-quic-session-preferred-address-ipv6.mjs index e9f23c3bf554d5..35e539848b4aba 100644 --- a/test/parallel/test-quic-session-preferred-address-ipv6.mjs +++ b/test/parallel/test-quic-session-preferred-address-ipv6.mjs @@ -100,6 +100,9 @@ const clientSession = await connect(serverEndpoint.address, { family: 'ipv6', }, }, + maxDatagramSendAttempts: 100, // While the connection is restablished, + // all the acknowledgement packets of ngtcp2 are counted as send attempts + // so either this or a delay, or a change in ngtcp2 interfaces }); await clientSession.opened; diff --git a/test/parallel/test-quic-session-preferred-address.mjs b/test/parallel/test-quic-session-preferred-address.mjs index c4a55ee4d42b74..6a526a8c9ca63f 100644 --- a/test/parallel/test-quic-session-preferred-address.mjs +++ b/test/parallel/test-quic-session-preferred-address.mjs @@ -78,6 +78,9 @@ const clientSession = await connect(serverEndpoint.address, { strictEqual(oldRemote, null); strictEqual(preferred, true); }), + maxDatagramSendAttempts: 100, // While the connection is restablished, + // all the acknowledgement packets of ngtcp2 are counted as send attempts + // so either this or a delay, or a change in ngtcp2 interfaces }); await clientSession.opened; From c0643f3c4e369aeb0c337d9122263ee8b3e43ecd Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sun, 5 Jul 2026 09:48:21 +0200 Subject: [PATCH 2/2] quic: fix lint --- src/quic/application.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/quic/application.cc b/src/quic/application.cc index c57ff91d043c57..e0950419580ec8 100644 --- a/src/quic/application.cc +++ b/src/quic/application.cc @@ -540,7 +540,8 @@ void Session::Application::SendPendingData() { // We call Application::ResumeStream directly (not Session::ResumeStream) // to avoid creating a SendPendingDataScope — we're already inside // SendPendingData and re-entering would just hit nwrite=0 again. - if (nwrite == 0 && (stream_data.id >= 0 || !session_->HasPendingDatagrams())) { + if (nwrite == 0 && + (stream_data.id >= 0 || !session_->HasPendingDatagrams())) { Debug(session_, "Congestion or not our turn to send"); if (stream_data.id >= 0 && (stream_data.count > 0 || stream_data.fin)) { ResumeStream(stream_data.id);