From 34020d4099a9173164e684162b8602a965c80da4 Mon Sep 17 00:00:00 2001 From: Hans Hjort Date: Wed, 3 Sep 2025 15:28:13 -0400 Subject: [PATCH 1/3] Add dialer timeouts to the HTTP clients --- router/router.go | 56 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/router/router.go b/router/router.go index 05d0d992334..02977afa9e8 100644 --- a/router/router.go +++ b/router/router.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "encoding/json" "fmt" + "net" "net/http" "os" "strings" @@ -138,32 +139,50 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R generalHttpClient := &http.Client{ Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - MaxConnsPerHost: cfg.Client.MaxConnsPerHost, - MaxIdleConns: cfg.Client.MaxIdleConns, - MaxIdleConnsPerHost: cfg.Client.MaxIdleConnsPerHost, - IdleConnTimeout: time.Duration(cfg.Client.IdleConnTimeout) * time.Second, - TLSClientConfig: &tls.Config{RootCAs: certPool}, + Proxy: http.ProxyFromEnvironment, + DialContext: defaultTransportDialContext(&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }), + MaxConnsPerHost: cfg.Client.MaxConnsPerHost, + MaxIdleConns: cfg.Client.MaxIdleConns, + MaxIdleConnsPerHost: cfg.Client.MaxIdleConnsPerHost, + IdleConnTimeout: time.Duration(cfg.Client.IdleConnTimeout) * time.Second, + TLSClientConfig: &tls.Config{RootCAs: certPool}, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, }, } cacheHttpClient := &http.Client{ Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - MaxConnsPerHost: cfg.CacheClient.MaxConnsPerHost, - MaxIdleConns: cfg.CacheClient.MaxIdleConns, - MaxIdleConnsPerHost: cfg.CacheClient.MaxIdleConnsPerHost, - IdleConnTimeout: time.Duration(cfg.CacheClient.IdleConnTimeout) * time.Second, + Proxy: http.ProxyFromEnvironment, + DialContext: defaultTransportDialContext(&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }), + MaxConnsPerHost: cfg.CacheClient.MaxConnsPerHost, + MaxIdleConns: cfg.CacheClient.MaxIdleConns, + MaxIdleConnsPerHost: cfg.CacheClient.MaxIdleConnsPerHost, + IdleConnTimeout: time.Duration(cfg.CacheClient.IdleConnTimeout) * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, }, } floorFechterHttpClient := &http.Client{ Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - MaxConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost, - MaxIdleConns: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns, - MaxIdleConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost, - IdleConnTimeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) * time.Second, + Proxy: http.ProxyFromEnvironment, + DialContext: defaultTransportDialContext(&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }), + MaxConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost, + MaxIdleConns: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns, + MaxIdleConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost, + IdleConnTimeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, }, } @@ -292,6 +311,11 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R return r, nil } +// Grabbing the same dialer context as the default transport uses +func defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { + return dialer.DialContext +} + // Shutdown closes any dependencies of the router that may need closing func (r *Router) Shutdown() { glog.Info("[PBS Router] shutting down") From 6ca73fb2ef1f09dc565bc94b3521f3691282f938 Mon Sep 17 00:00:00 2001 From: Hans Hjort Date: Wed, 3 Sep 2025 17:03:15 -0400 Subject: [PATCH 2/3] Add config options for new parameters --- config/config.go | 30 +++++++++++++++++++++++++----- router/router.go | 24 ++++++++++++------------ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/config/config.go b/config/config.go index de6d04b9307..111aef79a68 100644 --- a/config/config.go +++ b/config/config.go @@ -125,11 +125,14 @@ type PriceFloorFetcher struct { const MIN_COOKIE_SIZE_BYTES = 500 type HTTPClient struct { - MaxConnsPerHost int `mapstructure:"max_connections_per_host"` - MaxIdleConns int `mapstructure:"max_idle_connections"` - MaxIdleConnsPerHost int `mapstructure:"max_idle_connections_per_host"` - IdleConnTimeout int `mapstructure:"idle_connection_timeout_seconds"` - Throttle HTTPThrottle `mapstructure:"throttle"` + MaxConnsPerHost int `mapstructure:"max_connections_per_host"` + MaxIdleConns int `mapstructure:"max_idle_connections"` + MaxIdleConnsPerHost int `mapstructure:"max_idle_connections_per_host"` + IdleConnTimeout int `mapstructure:"idle_connection_timeout_seconds"` + TLSHandshakeTimeout int `mapstructure:"tls_handshake_timeout_seconds"` + ExpectContinueTimeout int `mapstructure:"expect_continue_timeout_seconds"` + NetDialer NetDialer `mapstructure:"dialer"` + Throttle HTTPThrottle `mapstructure:"throttle"` } type HTTPThrottle struct { @@ -145,6 +148,11 @@ type HTTPThrottle struct { ThrottleWindow int `mapstructure:"throttle_window"` } +type NetDialer struct { + Timeout int `mapstructure:"timeout_seconds"` + KeepAlive int `mapstructure:"keep_alive_seconds"` +} + func (cfg *Configuration) validate(v *viper.Viper) []error { var errs []error errs = cfg.AuctionTimeouts.validate(errs) @@ -963,6 +971,10 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("http_client.max_idle_connections", 400) v.SetDefault("http_client.max_idle_connections_per_host", 10) v.SetDefault("http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("http_client.tls_handshake_timeout_seconds", 10) + v.SetDefault("http_client.expect_continue_timeout_seconds", 1) + v.SetDefault("http_client.dialer.timeout_seconds", 30) + v.SetDefault("http_client.dialer.keep_alive_seconds", 30) v.SetDefault("http_client.throttle.enable_throttling", false) v.SetDefault("http_client.throttle.simulate_throttling_only", false) v.SetDefault("http_client.throttle.long_queue_wait_threshold_ms", 50) @@ -972,6 +984,10 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("http_client_cache.max_idle_connections", 10) v.SetDefault("http_client_cache.max_idle_connections_per_host", 2) v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60) + v.SetDefault("http_client_cache.tls_handshake_timeout_seconds", 10) + v.SetDefault("http_client_cache.expect_continue_timeout_seconds", 1) + v.SetDefault("http_client_cache.dialer.timeout_seconds", 30) + v.SetDefault("http_client_cache.dialer.keep_alive_seconds", 30) // no metrics configured by default (metrics{host|database|username|password}) v.SetDefault("metrics.disabled_metrics.account_adapter_details", false) v.SetDefault("metrics.disabled_metrics.account_debug", true) @@ -1220,6 +1236,10 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("price_floors.fetcher.http_client.max_idle_connections", 40) v.SetDefault("price_floors.fetcher.http_client.max_idle_connections_per_host", 2) v.SetDefault("price_floors.fetcher.http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("price_floors.fetcher.http_client.tls_handshake_timeout_seconds", 10) + v.SetDefault("price_floors.fetcher.http_client.expect_continue_timeout_seconds", 1) + v.SetDefault("price_floors.fetcher.http_client.dialer.timeout_seconds", 30) + v.SetDefault("price_floors.fetcher.http_client.dialer.keep_alive_seconds", 30) v.SetDefault("price_floors.fetcher.max_retries", 10) v.SetDefault("account_defaults.events_enabled", false) diff --git a/router/router.go b/router/router.go index 02977afa9e8..d66c081752e 100644 --- a/router/router.go +++ b/router/router.go @@ -141,16 +141,16 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: defaultTransportDialContext(&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, + Timeout: time.Duration(cfg.Client.NetDialer.Timeout) * time.Second, + KeepAlive: time.Duration(cfg.Client.NetDialer.KeepAlive) * time.Second, }), MaxConnsPerHost: cfg.Client.MaxConnsPerHost, MaxIdleConns: cfg.Client.MaxIdleConns, MaxIdleConnsPerHost: cfg.Client.MaxIdleConnsPerHost, IdleConnTimeout: time.Duration(cfg.Client.IdleConnTimeout) * time.Second, TLSClientConfig: &tls.Config{RootCAs: certPool}, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, + TLSHandshakeTimeout: time.Duration(cfg.Client.TLSHandshakeTimeout) * time.Second, + ExpectContinueTimeout: time.Duration(cfg.Client.ExpectContinueTimeout) * time.Second, }, } @@ -158,15 +158,15 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: defaultTransportDialContext(&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, + Timeout: time.Duration(cfg.CacheClient.NetDialer.Timeout) * time.Second, + KeepAlive: time.Duration(cfg.CacheClient.NetDialer.KeepAlive) * time.Second, }), MaxConnsPerHost: cfg.CacheClient.MaxConnsPerHost, MaxIdleConns: cfg.CacheClient.MaxIdleConns, MaxIdleConnsPerHost: cfg.CacheClient.MaxIdleConnsPerHost, IdleConnTimeout: time.Duration(cfg.CacheClient.IdleConnTimeout) * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, + TLSHandshakeTimeout: time.Duration(cfg.CacheClient.TLSHandshakeTimeout) * time.Second, + ExpectContinueTimeout: time.Duration(cfg.CacheClient.ExpectContinueTimeout) * time.Second, }, } @@ -174,15 +174,15 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: defaultTransportDialContext(&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, + Timeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.NetDialer.Timeout) * time.Second, + KeepAlive: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.NetDialer.KeepAlive) * time.Second, }), MaxConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost, MaxIdleConns: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns, MaxIdleConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConnsPerHost, IdleConnTimeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.IdleConnTimeout) * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, + TLSHandshakeTimeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.TLSHandshakeTimeout) * time.Second, + ExpectContinueTimeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.ExpectContinueTimeout) * time.Second, }, } From 831a1f4a5c3cf9045fd6be9b98edefe8a4acee32 Mon Sep 17 00:00:00 2001 From: Hans Hjort Date: Thu, 18 Sep 2025 10:55:17 -0400 Subject: [PATCH 3/3] Responding to PR comments --- config/config.go | 14 +++++++------- router/router.go | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/config/config.go b/config/config.go index 111aef79a68..d36f0e442a5 100644 --- a/config/config.go +++ b/config/config.go @@ -131,7 +131,7 @@ type HTTPClient struct { IdleConnTimeout int `mapstructure:"idle_connection_timeout_seconds"` TLSHandshakeTimeout int `mapstructure:"tls_handshake_timeout_seconds"` ExpectContinueTimeout int `mapstructure:"expect_continue_timeout_seconds"` - NetDialer NetDialer `mapstructure:"dialer"` + Dialer Dialer `mapstructure:"dialer"` Throttle HTTPThrottle `mapstructure:"throttle"` } @@ -148,9 +148,9 @@ type HTTPThrottle struct { ThrottleWindow int `mapstructure:"throttle_window"` } -type NetDialer struct { - Timeout int `mapstructure:"timeout_seconds"` - KeepAlive int `mapstructure:"keep_alive_seconds"` +type Dialer struct { + TimeoutSeconds int `mapstructure:"timeout_seconds"` + KeepAliveSeconds int `mapstructure:"keep_alive_seconds"` } func (cfg *Configuration) validate(v *viper.Viper) []error { @@ -974,7 +974,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("http_client.tls_handshake_timeout_seconds", 10) v.SetDefault("http_client.expect_continue_timeout_seconds", 1) v.SetDefault("http_client.dialer.timeout_seconds", 30) - v.SetDefault("http_client.dialer.keep_alive_seconds", 30) + v.SetDefault("http_client.dialer.keep_alive_seconds", 15) v.SetDefault("http_client.throttle.enable_throttling", false) v.SetDefault("http_client.throttle.simulate_throttling_only", false) v.SetDefault("http_client.throttle.long_queue_wait_threshold_ms", 50) @@ -987,7 +987,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("http_client_cache.tls_handshake_timeout_seconds", 10) v.SetDefault("http_client_cache.expect_continue_timeout_seconds", 1) v.SetDefault("http_client_cache.dialer.timeout_seconds", 30) - v.SetDefault("http_client_cache.dialer.keep_alive_seconds", 30) + v.SetDefault("http_client_cache.dialer.keep_alive_seconds", 15) // no metrics configured by default (metrics{host|database|username|password}) v.SetDefault("metrics.disabled_metrics.account_adapter_details", false) v.SetDefault("metrics.disabled_metrics.account_debug", true) @@ -1239,7 +1239,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("price_floors.fetcher.http_client.tls_handshake_timeout_seconds", 10) v.SetDefault("price_floors.fetcher.http_client.expect_continue_timeout_seconds", 1) v.SetDefault("price_floors.fetcher.http_client.dialer.timeout_seconds", 30) - v.SetDefault("price_floors.fetcher.http_client.dialer.keep_alive_seconds", 30) + v.SetDefault("price_floors.fetcher.http_client.dialer.keep_alive_seconds", 15) v.SetDefault("price_floors.fetcher.max_retries", 10) v.SetDefault("account_defaults.events_enabled", false) diff --git a/router/router.go b/router/router.go index d66c081752e..4bc156a1577 100644 --- a/router/router.go +++ b/router/router.go @@ -141,8 +141,8 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: defaultTransportDialContext(&net.Dialer{ - Timeout: time.Duration(cfg.Client.NetDialer.Timeout) * time.Second, - KeepAlive: time.Duration(cfg.Client.NetDialer.KeepAlive) * time.Second, + Timeout: time.Duration(cfg.Client.Dialer.TimeoutSeconds) * time.Second, + KeepAlive: time.Duration(cfg.Client.Dialer.KeepAliveSeconds) * time.Second, }), MaxConnsPerHost: cfg.Client.MaxConnsPerHost, MaxIdleConns: cfg.Client.MaxIdleConns, @@ -158,8 +158,8 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: defaultTransportDialContext(&net.Dialer{ - Timeout: time.Duration(cfg.CacheClient.NetDialer.Timeout) * time.Second, - KeepAlive: time.Duration(cfg.CacheClient.NetDialer.KeepAlive) * time.Second, + Timeout: time.Duration(cfg.CacheClient.Dialer.TimeoutSeconds) * time.Second, + KeepAlive: time.Duration(cfg.CacheClient.Dialer.KeepAliveSeconds) * time.Second, }), MaxConnsPerHost: cfg.CacheClient.MaxConnsPerHost, MaxIdleConns: cfg.CacheClient.MaxIdleConns, @@ -174,8 +174,8 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: defaultTransportDialContext(&net.Dialer{ - Timeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.NetDialer.Timeout) * time.Second, - KeepAlive: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.NetDialer.KeepAlive) * time.Second, + Timeout: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.Dialer.TimeoutSeconds) * time.Second, + KeepAlive: time.Duration(cfg.PriceFloors.Fetcher.HttpClient.Dialer.KeepAliveSeconds) * time.Second, }), MaxConnsPerHost: cfg.PriceFloors.Fetcher.HttpClient.MaxConnsPerHost, MaxIdleConns: cfg.PriceFloors.Fetcher.HttpClient.MaxIdleConns, @@ -311,7 +311,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R return r, nil } -// Grabbing the same dialer context as the default transport uses +// defaultTransportDialContext returns the same dialer context as the default transport uses, copied from the library code. func defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { return dialer.DialContext }