Loading src/posix/tcp.cpp +14 −0 Original line number Diff line number Diff line Loading @@ -409,6 +409,14 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) // in errno from the ::connect() call above. errno = err; } } else { // waitWrite() returned false: the attempt neither succeeded nor failed // within kConnectAttemptTimeoutMs, it just never finished -- errno is still // EINPROGRESS from the ::connect() call above, which strerror()'s to the // misleading "Operation now in progress" (sounds like nothing is wrong yet) // instead of describing what actually happened: this address didn't respond // in time. errno = ETIMEDOUT; } ::close(_Socket); Loading Loading @@ -523,6 +531,12 @@ void tcp::connectTimeout(const std::string& addr, int port, int timeout_ms) { // in errno from the ::connect() call above. errno = err; } } else { // Timed out (see tcp::connect()'s identical branch above for why this // matters): without this, errno is still the stale EINPROGRESS from the // ::connect() call above, and the final exception below would claim // "Operation now in progress" for what was actually a timeout. errno = ETIMEDOUT; } } Loading src/socket.h +6 −0 Original line number Diff line number Diff line Loading @@ -483,6 +483,12 @@ namespace netplus { void handshake_after_accept() override { _tls.handshake_after_accept(); } void handshake_after_connect() override { _tls.handshake_after_connect(); } void connect(const std::string& addr, int port, bool nonblock = false) override; // Unlike connect(nonblock=false)'s hardcoded 15s handshake deadline (independent of // anything the caller asked for), this bounds the TCP connect AND the TLS handshake // together against one caller-supplied budget -- see ssl.cpp for why that matters for // a reverse proxy that needs to fail over to a healthy backend quickly instead of // waiting out a fixed worst-case on a dead/unresponsive one. void connectTimeout(const std::string& addr, int port, int timeout_ms) override; void close() override; size_t sendData(buffer& data, int flags = 0) override; Loading src/ssl.cpp +48 −0 Original line number Diff line number Diff line Loading @@ -170,6 +170,54 @@ void ssl::connect(const std::string& addr, int port, bool nonblock) { } } // Same idea as connect(nonblock=false), but bounds the TCP connect AND the TLS handshake // together against one overall timeout_ms budget instead of a fixed worst-case (connect()'s // underlying tcp::connect() spends up to 10s *per resolved address*, then connect() itself // gives the handshake a further fixed 15s on top -- up to ~35s total for one dead/unresponsive // backend, regardless of anything a caller like HttpClient configured). A reverse proxy that // needs to fail over to a healthy backend quickly instead of hanging a client's request on a // dead one needs that whole budget to be one number it can actually turn down. void ssl::connectTimeout(const std::string& addr, int port, int timeout_ms) { const auto start = std::chrono::steady_clock::now(); // Spends only whatever's left of timeout_ms trying every resolved address (see // tcp::connectTimeout()'s own doc comment) -- leaves the socket blocking on success, same // post-condition connect(nonblock=false) leaves it in before the handshake loop below. tcp::connectTimeout(addr, port, timeout_ms); _tls.is_client = true; _tls.hostname = addr; _tls.setSocket(this); _tls.queueClientHello(); _tls.hs_state = tls::HsState::CLI_FLUSH_CH; socketwait sw; const auto deadline = start + std::chrono::milliseconds(timeout_ms); while (!_tls.getHandshakeDone()) { if (std::chrono::steady_clock::now() >= deadline) { NetException e; e[NetException::Error] << "ssl::connectTimeout: TLS handshake timeout"; throw e; } while (_tls.hasPendingWrite()) { _tls.flush_out(); } try { _tls.handshake_after_connect(); } catch (NetException &e) { if (e.getErrorType() == NetException::Note) { // Need more data from peer -- wait for socket readability, but never past // this call's own deadline (waitRead's own timeout_ms is a per-iteration // poll, not the overall budget -- see connect()'s identical 100ms poll). sw.waitRead(*this, 100); continue; } throw; } } } // ============================================================================ // Close - Reset TLS state and close TCP connection // ============================================================================ Loading Loading
src/posix/tcp.cpp +14 −0 Original line number Diff line number Diff line Loading @@ -409,6 +409,14 @@ void netplus::tcp::connect(const std::string& addr, int port, bool nonblock) // in errno from the ::connect() call above. errno = err; } } else { // waitWrite() returned false: the attempt neither succeeded nor failed // within kConnectAttemptTimeoutMs, it just never finished -- errno is still // EINPROGRESS from the ::connect() call above, which strerror()'s to the // misleading "Operation now in progress" (sounds like nothing is wrong yet) // instead of describing what actually happened: this address didn't respond // in time. errno = ETIMEDOUT; } ::close(_Socket); Loading Loading @@ -523,6 +531,12 @@ void tcp::connectTimeout(const std::string& addr, int port, int timeout_ms) { // in errno from the ::connect() call above. errno = err; } } else { // Timed out (see tcp::connect()'s identical branch above for why this // matters): without this, errno is still the stale EINPROGRESS from the // ::connect() call above, and the final exception below would claim // "Operation now in progress" for what was actually a timeout. errno = ETIMEDOUT; } } Loading
src/socket.h +6 −0 Original line number Diff line number Diff line Loading @@ -483,6 +483,12 @@ namespace netplus { void handshake_after_accept() override { _tls.handshake_after_accept(); } void handshake_after_connect() override { _tls.handshake_after_connect(); } void connect(const std::string& addr, int port, bool nonblock = false) override; // Unlike connect(nonblock=false)'s hardcoded 15s handshake deadline (independent of // anything the caller asked for), this bounds the TCP connect AND the TLS handshake // together against one caller-supplied budget -- see ssl.cpp for why that matters for // a reverse proxy that needs to fail over to a healthy backend quickly instead of // waiting out a fixed worst-case on a dead/unresponsive one. void connectTimeout(const std::string& addr, int port, int timeout_ms) override; void close() override; size_t sendData(buffer& data, int flags = 0) override; Loading
src/ssl.cpp +48 −0 Original line number Diff line number Diff line Loading @@ -170,6 +170,54 @@ void ssl::connect(const std::string& addr, int port, bool nonblock) { } } // Same idea as connect(nonblock=false), but bounds the TCP connect AND the TLS handshake // together against one overall timeout_ms budget instead of a fixed worst-case (connect()'s // underlying tcp::connect() spends up to 10s *per resolved address*, then connect() itself // gives the handshake a further fixed 15s on top -- up to ~35s total for one dead/unresponsive // backend, regardless of anything a caller like HttpClient configured). A reverse proxy that // needs to fail over to a healthy backend quickly instead of hanging a client's request on a // dead one needs that whole budget to be one number it can actually turn down. void ssl::connectTimeout(const std::string& addr, int port, int timeout_ms) { const auto start = std::chrono::steady_clock::now(); // Spends only whatever's left of timeout_ms trying every resolved address (see // tcp::connectTimeout()'s own doc comment) -- leaves the socket blocking on success, same // post-condition connect(nonblock=false) leaves it in before the handshake loop below. tcp::connectTimeout(addr, port, timeout_ms); _tls.is_client = true; _tls.hostname = addr; _tls.setSocket(this); _tls.queueClientHello(); _tls.hs_state = tls::HsState::CLI_FLUSH_CH; socketwait sw; const auto deadline = start + std::chrono::milliseconds(timeout_ms); while (!_tls.getHandshakeDone()) { if (std::chrono::steady_clock::now() >= deadline) { NetException e; e[NetException::Error] << "ssl::connectTimeout: TLS handshake timeout"; throw e; } while (_tls.hasPendingWrite()) { _tls.flush_out(); } try { _tls.handshake_after_connect(); } catch (NetException &e) { if (e.getErrorType() == NetException::Note) { // Need more data from peer -- wait for socket readability, but never past // this call's own deadline (waitRead's own timeout_ms is a per-iteration // poll, not the overall budget -- see connect()'s identical 100ms poll). sw.waitRead(*this, 100); continue; } throw; } } } // ============================================================================ // Close - Reset TLS state and close TCP connection // ============================================================================ Loading