SMOODEV-2513: Add connect timeout to Go fetch SDK (parity with Rust #88) - #89
SMOODEV-2513: Add connect timeout to Go fetch SDK (parity with Rust #88)#89brentrager wants to merge 1 commit into
Conversation
Bounds only the connection-establishment phase, separate from the whole-request WithTimeout. When set (> 0), the SDK-constructed *http.Client uses an *http.Transport cloned from the default (preserving proxy/TLS/keepalive settings) whose dialer times out after the configured duration. A black-holed connect — e.g. a dead pod IP still lingering in a ClusterIP's iptables (SMOODEV-2498 / SMOODEV-2481) — then fails in ~that window and the configured retry can land on a live endpoint, instead of stalling until the whole-request timeout. Default-OFF: leaving it unset (0) preserves the previous no-connect-timeout behavior byte-for-byte. A caller-provided *http.Client (WithHTTPClient) is left untouched — the connect timeout only applies to the transport this SDK builds. Test mirrors the Rust connect_timeout_tests.rs: a request to a black-hole address (10.255.255.1:80) with a short connect timeout fails in ~that window, well under the 10x-larger whole-request timeout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Consolidates PRs #88 (Rust), #89 (Go), #90 (Python), #91 (.NET) and #92 (TypeScript), open since 2026-07-10, onto current main. The work was sound; it had gone stale against traceparent injection and the redaction fix, both of which touch the same single-request site. The gap is real: without a connect timeout, a SYN to a dead pod IP still lingering in a ClusterIP's iptables burns the WHOLE-request budget before retry can try a live endpoint. Measured locally, an unbounded connect to a black hole fails after 10.5s (undici's own default); bounded at 500ms it fails in ~1s. Two changes from the original PRs: - undici is an OPTIONAL PEER dependency, not a runtime dependency. #92 added it to `dependencies`, which would have put a full HTTP stack in the tree of every consumer -- including browser bundles -- for an opt-in Node-only feature. It follows the @opentelemetry/api pattern this package already uses: devDependency for our own tests, optional peer for consumers, lazy import at the call site. - Requesting a connect timeout without undici installed now THROWS with an actionable message rather than silently continuing unbounded. A timeout that quietly isn't applied is the exact failure this feature exists to prevent. Per Spec C, the five regression tests read their knobs (black-hole URL, connect timeout, whole timeout, elapsed ceiling) from the committed spec/connect-timeout-corpus.json instead of each hard-coding them, so the thresholds cannot drift apart per language. The TS suite carries a positive control so a corpus that failed to load reads red rather than green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
…103) Consolidates PRs #88 (Rust), #89 (Go), #90 (Python), #91 (.NET) and #92 (TypeScript), open since 2026-07-10, onto current main. The work was sound; it had gone stale against traceparent injection and the redaction fix, both of which touch the same single-request site. The gap is real: without a connect timeout, a SYN to a dead pod IP still lingering in a ClusterIP's iptables burns the WHOLE-request budget before retry can try a live endpoint. Measured locally, an unbounded connect to a black hole fails after 10.5s (undici's own default); bounded at 500ms it fails in ~1s. Two changes from the original PRs: - undici is an OPTIONAL PEER dependency, not a runtime dependency. #92 added it to `dependencies`, which would have put a full HTTP stack in the tree of every consumer -- including browser bundles -- for an opt-in Node-only feature. It follows the @opentelemetry/api pattern this package already uses: devDependency for our own tests, optional peer for consumers, lazy import at the call site. - Requesting a connect timeout without undici installed now THROWS with an actionable message rather than silently continuing unbounded. A timeout that quietly isn't applied is the exact failure this feature exists to prevent. Per Spec C, the five regression tests read their knobs (black-hole URL, connect timeout, whole timeout, elapsed ceiling) from the committed spec/connect-timeout-corpus.json instead of each hard-coding them, so the thresholds cannot drift apart per language. The TS suite carries a positive control so a corpus that failed to load reads red rather than green. Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Landed via #103, which consolidates all five ports onto current main. The work here was sound — it had gone stale against traceparent injection (#95) and the credential-redaction fix (#98), both of which touch the same single-request site. Two changes from this PR in the consolidated version:
Per Spec C the five regression tests now read their knobs (black-hole URL, connect timeout, whole timeout, elapsed ceiling) from Closing in favour of #103. |
Problem
Go parity for Rust #88 (SMOODEV-2513). Without a connect timeout, a black-holed connect — e.g. a dead pod IP still lingering in a ClusterIP's iptables (SMOODEV-2498 / SMOODEV-2481) — stalls until the whole-request timeout (~16s stalls observed in api-prime) before retry can land on a live endpoint.
Solution
ConnectTimeoutfield +WithConnectTimeout(d time.Duration)builder method, mirroringWithTimeout.> 0), the SDK-constructed*http.Clientuses an*http.Transportcloned fromhttp.DefaultTransport(preserving proxy/TLS/keep-alive) whoseDialContextis anet.Dialer{Timeout: connectTimeout}. Only the connection-establishment phase is bounded; slow-but-alive handlers are unaffected.Default-OFF
Leaving it unset (
0) leaves the transport untouched → behavior byte-identical to before, so existing callers are unaffected. A caller-provided*http.Client(viaWithHTTPClient) is also left untouched — the connect timeout only applies to the transport this SDK constructs.Test
Mirrors the Rust
connect_timeout_tests.rs: a request to a black-hole address (http://10.255.255.1:80/) with a 500ms connect timeout fails in ~that window (verified 0.50s), well under the 10x-larger 5s whole-request timeout. Plus a default-OFF test asserting no custom transport when unset.gofmt/go vetclean,go test ./...97 passed.🤖 Generated with Claude Code