feat: add per-phase timeout value type to sdk-core#150
Conversation
Introduce an immutable Timeout value type splitting a call's I/O budget into connect, read, write, and request phases. The read and write phases inherit the request timeout when left unset, so a caller can express "finish within N seconds" with a single value while still being able to pin individual phases. Connect establishment is kept separate and does not inherit. Every phase uses Duration.ZERO to mean "no limit", matching the underlying HTTP clients this maps onto. The builder validates durations are non-negative and nanosecond- representable. This is a core value type only; transports translate the resolved phases onto their native settings. It is deliberately independent of the retry total-timeout budget — a Timeout bounds a single attempt's phases, while the retry budget bounds the sum of all attempts plus backoff, and the two compose. Wire it into the configuration surface with well-known keys (CONNECT_TIMEOUT, READ_TIMEOUT, WRITE_TIMEOUT, REQUEST_TIMEOUT) and a Configuration.getTimeout() accessor that resolves a Timeout from those keys, preserving the value type's inheritance and never throwing on a malformed value. Closes #41
|
This adds a standalone, immutable per-phase Issues
|
Summary
Adds a per-phase
Timeoutvalue type tosdk-corethat splits a single call's I/O budget into four phases a transport can bound independently:connectTimeout— connection establishment (and TLS handshake)readTimeout— waiting for response byteswriteTimeout— flushing request bytesrequestTimeout— the end-to-end ceiling for the whole callThe read and write phases inherit
requestTimeoutwhen left unset, so a caller who only knows "this call must finish within N seconds" can set the request budget alone and have read/write follow.connectTimeoutis intentionally separate and does not inherit. Every phase usesDuration.ZEROto mean "no limit", matching the convention of the clients this maps onto (OkHttp,java.net.http). The builder rejects negative or unrepresentable durations.The type is a hand-written, immutable value type following the existing SDK conventions (private constructor +
BuilderimplementingBuilder<T>+ prefillednewBuilder(),@JvmStatic/@JvmOverloadsfor Java callers).effectiveReadTimeout/effectiveWriteTimeoutresolve the inheritance so transport adapters can read the value a phase should actually enforce without re-implementing the fallback.Independent of the retry total-timeout
A
Timeoutbounds a single attempt's I/O phases; the retry total-timeout (RetrySettings.totalTimeout) bounds the sum of all attempts plus backoff. Neither derives from the other, and the KDoc documents the separation. Transports translate the resolved phases to native settings — that wiring is out of scope here.Configuration surface
Exposed through
Configurationwith four well-known keys (CONNECT_TIMEOUT,READ_TIMEOUT,WRITE_TIMEOUT,REQUEST_TIMEOUT) and agetTimeout()accessor that resolves aTimeoutfrom those keys using the existing duration grammar (ISO-8601 + shorthand). Consistent with the other typed accessors, a missing or malformed value never throws — it behaves as if the key were absent and falls back to the supplied default / inheritance.Tests
TimeoutTest— defaults/NONE, read/write inheritance, explicit pinning (including pinning a phase to zero so it does not inherit), connect-does-not-inherit, all-phases-set, non-negative + representability validation,newBuilderround-trip preserving explicit flags, andequals/hashCode/toString.ConfigurationTimeoutTest— key-to-phase mapping, request-driven inheritance, malformed-value tolerance, default fill, and env/sysprop layering.Gated build (scoped,
--no-daemon)Result: BUILD SUCCESSFUL.
:sdk-core:apiDumpwas run and the regeneratedsdk-core/api/sdk-core.apiis committed.Closes #41