From 48bb17e1f9e69d1e14d033e2d1ecc136b0b2679d Mon Sep 17 00:00:00 2001 From: wetcod <37023735+wetcod@users.noreply.github.com> Date: Wed, 3 Feb 2021 11:23:12 +0900 Subject: [PATCH] chore: config timeout and connection pool (#150) (#171) --- config/config.go | 38 +++++++++++++++++++++++++- config/toml.go | 25 ++++++++++++++++- node/node.go | 3 ++ rpc/jsonrpc/client/http_json_client.go | 13 +++++++-- rpc/jsonrpc/server/http_server.go | 5 ++++ 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index 15b3ab092..325deface 100644 --- a/config/config.go +++ b/config/config.go @@ -332,6 +332,30 @@ type RPCConfig struct { // 1024 - 40 - 10 - 50 = 924 = ~900 MaxOpenConnections int `mapstructure:"max_open_connections"` + // mirrors http.Server#ReadTimeout + // ReadTimeout is the maximum duration for reading the entire + // request, including the body. + // + // Because ReadTimeout does not let Handlers make per-request + // decisions on each request body's acceptable deadline or + // upload rate, most users will prefer to use + // ReadHeaderTimeout. It is valid to use them both. + ReadTimeout time.Duration `mapstructure:"read_timeout"` + + // mirrors http.Server#WriteTimeout + // WriteTimeout is the maximum duration before timing out + // writes of the response. It is reset whenever a new + // request's header is read. Like ReadTimeout, it does not + // let Handlers make decisions on a per-request basis. + WriteTimeout time.Duration `mapstructure:"write_timeout"` + + // mirrors http.Server#IdleTimeout + // IdleTimeout is the maximum amount of time to wait for the + // next request when keep-alives are enabled. If IdleTimeout + // is zero, the value of ReadTimeout is used. If both are + // zero, there is no timeout. + IdleTimeout time.Duration `mapstructure:"idle_timeout"` + // Maximum number of unique clientIDs that can /subscribe // If you're using /broadcast_tx_commit, set to the estimated maximum number // of broadcast_tx_commit calls per block. @@ -343,7 +367,7 @@ type RPCConfig struct { MaxSubscriptionsPerClient int `mapstructure:"max_subscriptions_per_client"` // How long to wait for a tx to be committed during /broadcast_tx_commit - // WARNING: Using a value larger than 10s will result in increasing the + // WARNING: Using a value larger than 'WriteTimeout' will result in increasing the // global HTTP write timeout, which applies to all connections and endpoints. // See https://github.com/tendermint/tendermint/issues/3435 TimeoutBroadcastTxCommit time.Duration `mapstructure:"timeout_broadcast_tx_commit"` @@ -388,6 +412,9 @@ func DefaultRPCConfig() *RPCConfig { Unsafe: false, MaxOpenConnections: 900, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 60 * time.Second, MaxSubscriptionClients: 100, MaxSubscriptionsPerClient: 5, @@ -419,6 +446,15 @@ func (cfg *RPCConfig) ValidateBasic() error { if cfg.MaxOpenConnections < 0 { return errors.New("max_open_connections can't be negative") } + if cfg.ReadTimeout < 0 { + return errors.New("read_timeout can't be negative") + } + if cfg.WriteTimeout < 0 { + return errors.New("write_timeout can't be negative") + } + if cfg.IdleTimeout < 0 { + return errors.New("idle_timeout can't be negative") + } if cfg.MaxSubscriptionClients < 0 { return errors.New("max_subscription_clients can't be negative") } diff --git a/config/toml.go b/config/toml.go index 82ecdda20..50ac822e9 100644 --- a/config/toml.go +++ b/config/toml.go @@ -196,6 +196,29 @@ unsafe = {{ .RPC.Unsafe }} # 1024 - 40 - 10 - 50 = 924 = ~900 max_open_connections = {{ .RPC.MaxOpenConnections }} +# mirrors http.Server#ReadTimeout +# ReadTimeout is the maximum duration for reading the entire +# request, including the body. +# Because ReadTimeout does not let Handlers make per-request +# decisions on each request body's acceptable deadline or +# upload rate, most users will prefer to use +# ReadHeaderTimeout. It is valid to use them both. +read_timeout = "{{ .RPC.ReadTimeout }}" + +# mirrors http.Server#WriteTimeout +# WriteTimeout is the maximum duration before timing out +# writes of the response. It is reset whenever a new +# request's header is read. Like ReadTimeout, it does not +# let Handlers make decisions on a per-request basis. +write_timeout = "{{ .RPC.WriteTimeout }}" + +# mirrors http.Server#IdleTimeout +# IdleTimeout is the maximum amount of time to wait for the +# next request when keep-alives are enabled. If IdleTimeout +# is zero, the value of ReadTimeout is used. If both are +# zero, there is no timeout. +idle_timeout = "{{ .RPC.IdleTimeout }}" + # Maximum number of unique clientIDs that can /subscribe # If you're using /broadcast_tx_commit, set to the estimated maximum number # of broadcast_tx_commit calls per block. @@ -207,7 +230,7 @@ max_subscription_clients = {{ .RPC.MaxSubscriptionClients }} max_subscriptions_per_client = {{ .RPC.MaxSubscriptionsPerClient }} # How long to wait for a tx to be committed during /broadcast_tx_commit. -# WARNING: Using a value larger than 10s will result in increasing the +# WARNING: Using a value larger than 'WriteTimeout' will result in increasing the # global HTTP write timeout, which applies to all connections and endpoints. # See https://github.com/tendermint/tendermint/issues/3435 timeout_broadcast_tx_commit = "{{ .RPC.TimeoutBroadcastTxCommit }}" diff --git a/node/node.go b/node/node.go index b309f995b..eef38e866 100644 --- a/node/node.go +++ b/node/node.go @@ -1011,6 +1011,9 @@ func (n *Node) startRPC() ([]net.Listener, error) { config.MaxBodyBytes = n.config.RPC.MaxBodyBytes config.MaxHeaderBytes = n.config.RPC.MaxHeaderBytes config.MaxOpenConnections = n.config.RPC.MaxOpenConnections + config.ReadTimeout = n.config.RPC.ReadTimeout + config.WriteTimeout = n.config.RPC.WriteTimeout + config.IdleTimeout = n.config.RPC.IdleTimeout // If necessary adjust global WriteTimeout to ensure it's greater than // TimeoutBroadcastTxCommit. // See https://github.com/tendermint/tendermint/issues/3435 diff --git a/rpc/jsonrpc/client/http_json_client.go b/rpc/jsonrpc/client/http_json_client.go index 59727390a..2e3822a3b 100644 --- a/rpc/jsonrpc/client/http_json_client.go +++ b/rpc/jsonrpc/client/http_json_client.go @@ -10,6 +10,7 @@ import ( "net/http" "net/url" "strings" + "time" tmsync "github.com/tendermint/tendermint/libs/sync" types "github.com/tendermint/tendermint/rpc/jsonrpc/types" @@ -21,6 +22,10 @@ const ( protoWSS = "wss" protoWS = "ws" protoTCP = "tcp" + + defaultMaxIdleConns = 10000 + defaultIdleConnTimeout = 60 // sec + defaultExpectContinueTimeout = 1 // sec ) //------------------------------------------------------------- @@ -369,8 +374,12 @@ func DefaultHTTPClient(remoteAddr string) (*http.Client, error) { client := &http.Client{ Transport: &http.Transport{ // Set to true to prevent GZIP-bomb DoS attacks - DisableCompression: true, - Dial: dialFn, + DisableCompression: true, + Dial: dialFn, + MaxIdleConns: defaultMaxIdleConns, + MaxIdleConnsPerHost: defaultMaxIdleConns, + IdleConnTimeout: defaultIdleConnTimeout * time.Second, + ExpectContinueTimeout: defaultExpectContinueTimeout * time.Second, }, } diff --git a/rpc/jsonrpc/server/http_server.go b/rpc/jsonrpc/server/http_server.go index 6799d3665..8b0f247ff 100644 --- a/rpc/jsonrpc/server/http_server.go +++ b/rpc/jsonrpc/server/http_server.go @@ -27,6 +27,8 @@ type Config struct { ReadTimeout time.Duration // mirrors http.Server#WriteTimeout WriteTimeout time.Duration + // mirrors http.Server#IdleTimeout + IdleTimeout time.Duration // MaxBodyBytes controls the maximum number of bytes the // server will read parsing the request body. MaxBodyBytes int64 @@ -40,6 +42,7 @@ func DefaultConfig() *Config { MaxOpenConnections: 0, // unlimited ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, + IdleTimeout: 60 * time.Second, MaxBodyBytes: int64(1000000), // 1MB MaxHeaderBytes: 1 << 20, // same as the net/http default } @@ -56,6 +59,7 @@ func Serve(listener net.Listener, handler http.Handler, logger log.Logger, confi Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger), ReadTimeout: config.ReadTimeout, WriteTimeout: config.WriteTimeout, + IdleTimeout: config.IdleTimeout, MaxHeaderBytes: config.MaxHeaderBytes, } err := s.Serve(listener) @@ -81,6 +85,7 @@ func ServeTLS( Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger), ReadTimeout: config.ReadTimeout, WriteTimeout: config.WriteTimeout, + IdleTimeout: config.IdleTimeout, MaxHeaderBytes: config.MaxHeaderBytes, } err := s.ServeTLS(listener, certFile, keyFile)