From b50c2c2f7a5eb47ba7644282bc8ce0f0af74a3cb Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Fri, 17 Jul 2026 09:45:18 +0530 Subject: [PATCH] fix(sdk-go): improve HTTP client and make jitter thread-safe - Add HTTP transport with ResponseHeaderTimeout for better timeout control - Replace global math/rand with per-process seed using crypto/rand - Add atomic counter for thread-safe jitter randomization --- client.go | 8 ++++++-- retry.go | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 614dbd4..09a1409 100644 --- a/client.go +++ b/client.go @@ -48,8 +48,12 @@ func WithAPIKey(key string) ClientOption { // retries with exponential backoff on transient failures. func New(opts ...ClientOption) *Client { c := &Client{ - baseURL: defaultBaseURL, - httpClient: &http.Client{Timeout: 30 * time.Second}, + baseURL: defaultBaseURL, + httpClient: &http.Client{ + Transport: &http.Transport{ + ResponseHeaderTimeout: 5 * time.Second, + }, + }, } for _, opt := range opts { opt(c) diff --git a/retry.go b/retry.go index ee2885f..ebb0990 100644 --- a/retry.go +++ b/retry.go @@ -3,13 +3,27 @@ package hawksdk import ( "bytes" "context" + cryptorand "crypto/rand" + "encoding/binary" "io" "math" "math/rand" "net/http" + "sync/atomic" "time" ) +// jitterSeed provides a per-process random seed for jitter, avoiding the +// global math/rand state which is not safe for concurrent use in some +// runtimes and can produce predictable sequences. +var jitterSeed = atomic.Int64{} + +func init() { + var buf [8]byte + _, _ = cryptorand.Read(buf[:]) + jitterSeed.Store(int64(binary.LittleEndian.Uint64(buf[:]))) +} + // RetryConfig configures the automatic retry behavior for API requests. type RetryConfig struct { // MaxRetries is the maximum number of retry attempts. @@ -83,7 +97,10 @@ func (cfg *RetryConfig) backoffDuration(attempt int) time.Duration { backoff = float64(cfg.MaxBackoff) } // Full jitter: random value between 0 and calculated backoff. - jittered := time.Duration(rand.Float64() * backoff) + // Uses a per-process seed to avoid the global math/rand state. + seed := jitterSeed.Add(1) + rng := rand.New(rand.NewSource(seed)) + jittered := time.Duration(rng.Float64() * backoff) return jittered }