From f14b85694fbd2de19555f1208690bdd8355cab24 Mon Sep 17 00:00:00 2001 From: catbot Date: Tue, 25 Aug 2026 17:06:51 +0000 Subject: [PATCH] fix(quic): bound the client handshake QUIC_SETTINGS never set HandshakeIdleTimeoutMs, so it inherited the 120s idle timeout. The constructor blocks until msquic reports the connection either up or shut down, which meant dialling a host that does not answer parked the caller for minutes. Bound it to 10s. Co-Authored-By: Claude Opus 5 --- implementations/Crafter.Network-ClientQUIC.cpp | 8 ++++++++ interfaces/Crafter.Network-ClientQUIC.cppm | 2 ++ 2 files changed, 10 insertions(+) diff --git a/implementations/Crafter.Network-ClientQUIC.cpp b/implementations/Crafter.Network-ClientQUIC.cpp index dbe16df..1857f59 100644 --- a/implementations/Crafter.Network-ClientQUIC.cpp +++ b/implementations/Crafter.Network-ClientQUIC.cpp @@ -555,6 +555,12 @@ static HQUIC OpenClientConfiguration(const std::string& alpn, const QUICClientCr QUIC_SETTINGS settings{}; settings.IsSet.IdleTimeoutMs = 1; settings.IdleTimeoutMs = 120'000; + // Bound the handshake separately from the (much longer) idle timeout. + // The constructor blocks until msquic reports the connection either up or + // shut down; without this, dialling a host that never answers leaves it + // waiting for the idle timeout — minutes rather than seconds. + settings.IsSet.HandshakeIdleTimeoutMs = 1; + settings.HandshakeIdleTimeoutMs = 10'000; // Keep the connection alive across long idle gaps. msquic sends PING frames // on its own timer thread (independent of the app), so a request/response // connection survives even while the app is blocked for a long time between @@ -618,6 +624,8 @@ ClientQUIC::ClientQUIC(const char* host, std::uint16_t port, std::string alpnIn, QUIC_STATUS handshakeStatus = QUIC_STATUS_SUCCESS; { std::unique_lock lk(impl->mtx); + // Bounded by HandshakeIdleTimeoutMs above: an unreachable peer ends in + // SHUTDOWN_INITIATED_BY_TRANSPORT rather than waiting here forever. impl->cv.wait(lk, [&]{ return impl->connected || impl->closed; }); if (impl->connected) return; handshakeStatus = impl->shutdownStatus; diff --git a/interfaces/Crafter.Network-ClientQUIC.cppm b/interfaces/Crafter.Network-ClientQUIC.cppm index 6068922..d93c5e0 100644 --- a/interfaces/Crafter.Network-ClientQUIC.cppm +++ b/interfaces/Crafter.Network-ClientQUIC.cppm @@ -180,6 +180,8 @@ namespace Crafter { // Client constructor: connects to host:port using QUIC. ALPN must // match the listener. Throws QUICException on connect failure. + // Blocks for at most the handshake idle timeout (10s), so a host that + // never answers fails in seconds rather than at the idle timeout. ClientQUIC(const char* host, std::uint16_t port, std::string alpn, QUICClientCredentials creds = {}); ClientQUIC(std::string host, std::uint16_t port, std::string alpn,