From 93b8a701d0edcacdcc95e35ade542ee487681d74 Mon Sep 17 00:00:00 2001 From: parthshah1 Date: Mon, 8 Sep 2025 12:13:46 -0400 Subject: [PATCH 1/2] adding rpc timeouts to gateway --- cmd/lotus-gateway/main.go | 28 ++++++++++++++++++ gateway/node.go | 60 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/cmd/lotus-gateway/main.go b/cmd/lotus-gateway/main.go index e897f563c45..df5926fed19 100644 --- a/cmd/lotus-gateway/main.go +++ b/cmd/lotus-gateway/main.go @@ -172,6 +172,26 @@ var runCmd = &cli.Command{ Usage: "Enable logging of incoming API requests. Note: This will log POST request bodies which may impact performance due to body buffering and may expose sensitive data in logs", Value: false, }, + &cli.DurationFlag{ + Name: "read-timeout", + Usage: "Maximum duration for reading the entire request, including the body. Use 0 to disable", + Value: gateway.ReadTimeout, + }, + &cli.DurationFlag{ + Name: "write-timeout", + Usage: "Maximum duration before timing out writes of the response. Use 0 to disable (sensitive setting - be careful)", + Value: gateway.WriteTimeout, + }, + &cli.DurationFlag{ + Name: "idle-timeout", + Usage: "Maximum amount of time to wait for the next request when keep-alives are enabled. Use 0 to disable", + Value: gateway.IdleTimeout, + }, + &cli.IntFlag{ + Name: "max-header-bytes", + Usage: "Maximum number of bytes the server will read parsing the request header's keys and values", + Value: gateway.MaxHeaderBytes, + }, }, Action: func(cctx *cli.Context) error { log.Info("Starting lotus gateway") @@ -208,6 +228,10 @@ var runCmd = &cli.Command{ maxFiltersPerConn = cctx.Int("eth-max-filters-per-conn") enableCORS = cctx.Bool("cors") enableRequestLogging = cctx.Bool("request-logging") + readTimeout = cctx.Duration("read-timeout") + writeTimeout = cctx.Duration("write-timeout") + idleTimeout = cctx.Duration("idle-timeout") + maxHeaderBytes = cctx.Int("max-header-bytes") ) serverOptions := make([]jsonrpc.ServerOption, 0) @@ -236,6 +260,10 @@ var runCmd = &cli.Command{ gateway.WithRateLimit(globalRateLimit), gateway.WithRateLimitTimeout(rateLimitTimeout), gateway.WithEthMaxFiltersPerConn(maxFiltersPerConn), + gateway.WithReadTimeout(readTimeout), + gateway.WithWriteTimeout(writeTimeout), + gateway.WithIdleTimeout(idleTimeout), + gateway.WithMaxHeaderBytes(maxHeaderBytes), ) handler, err := gateway.Handler( gwapi, diff --git a/gateway/node.go b/gateway/node.go index ac9635f5468..4fef49bf19b 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -36,6 +36,11 @@ const ( stateRateLimitTokens = 3 MaxRateLimitTokens = stateRateLimitTokens // Number of tokens consumed for the most expensive types of operations + ReadHeaderTimeout = time.Second * 10 // Maximum duration to wait for request headers to be read + ReadTimeout = time.Second * 60 // Maximum duration for reading the entire request, including the body + WriteTimeout = 0 // Maximum duration before timing out writes of the response (disabled by default) + IdleTimeout = time.Second * 60 // Maximum amount of time to wait for the next request when keep-alives are enabled + MaxHeaderBytes = 1 << 20 // Maximum number of bytes the server will read parsing the request header's keys and values (1MB) ) type Node struct { @@ -46,6 +51,11 @@ type Node struct { rateLimiter *rate.Limiter rateLimitTimeout time.Duration ethMaxFiltersPerConn int + readHeaderTimeout time.Duration + readTimeout time.Duration + writeTimeout time.Duration + idleTimeout time.Duration + maxHeaderBytes int errLookback error } @@ -57,6 +67,11 @@ type options struct { rateLimit int rateLimitTimeout time.Duration ethMaxFiltersPerConn int + ReadHeaderTimeout time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + IdleTimeout time.Duration + MaxHeaderBytes int } type Option func(*options) @@ -115,6 +130,41 @@ func WithEthMaxFiltersPerConn(ethMaxFiltersPerConn int) Option { } } +// WithReadHeaderTimeout sets the maximum duration to wait for request headers to be read. +func WithReadHeaderTimeout(readHeaderTimeout time.Duration) Option { + return func(opts *options) { + opts.ReadHeaderTimeout = readHeaderTimeout + } +} + +// WithReadTimeout sets the maximum duration for reading the entire request, including the body. +func WithReadTimeout(readTimeout time.Duration) Option { + return func(opts *options) { + opts.ReadTimeout = readTimeout + } +} + +// WithWriteTimeout sets the maximum duration before timing out writes of the response. +func WithWriteTimeout(writeTimeout time.Duration) Option { + return func(opts *options) { + opts.WriteTimeout = writeTimeout + } +} + +// WithIdleTimeout sets the maximum amount of time to wait for the next request when keep-alives are enabled. +func WithIdleTimeout(idleTimeout time.Duration) Option { + return func(opts *options) { + opts.IdleTimeout = idleTimeout + } +} + +// WithMaxHeaderBytes sets the maximum number of bytes the server will read parsing the request header's keys and values. +func WithMaxHeaderBytes(maxHeaderBytes int) Option { + return func(opts *options) { + opts.MaxHeaderBytes = maxHeaderBytes + } +} + // NewNode creates a new gateway node. func NewNode(v1 v1api.FullNode, v2 v2api.FullNode, opts ...Option) *Node { options := &options{ @@ -122,6 +172,11 @@ func NewNode(v1 v1api.FullNode, v2 v2api.FullNode, opts ...Option) *Node { maxMessageLookbackEpochs: DefaultMaxMessageLookbackEpochs, rateLimitTimeout: DefaultRateLimitTimeout, ethMaxFiltersPerConn: DefaultEthMaxFiltersPerConn, + ReadHeaderTimeout: ReadHeaderTimeout, + ReadTimeout: ReadTimeout, + WriteTimeout: WriteTimeout, + IdleTimeout: IdleTimeout, + MaxHeaderBytes: MaxHeaderBytes, } for _, opt := range opts { opt(options) @@ -138,6 +193,11 @@ func NewNode(v1 v1api.FullNode, v2 v2api.FullNode, opts ...Option) *Node { rateLimitTimeout: options.rateLimitTimeout, errLookback: fmt.Errorf("lookbacks of more than %s are disallowed", options.maxLookbackDuration), ethMaxFiltersPerConn: options.ethMaxFiltersPerConn, + readHeaderTimeout: options.ReadHeaderTimeout, + readTimeout: options.ReadTimeout, + writeTimeout: options.WriteTimeout, + idleTimeout: options.IdleTimeout, + maxHeaderBytes: options.MaxHeaderBytes, } gateway.v1Proxy = &reverseProxyV1{ gateway: gateway, From 9cf25a59583d06edea2a475594afd4bdb101c91d Mon Sep 17 00:00:00 2001 From: parthshah1 Date: Mon, 8 Sep 2025 14:41:22 -0400 Subject: [PATCH 2/2] Adding changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be8b0693423..eff0f4025e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ This is the Lotus v1.33.1 release, which introduces performance improvements and - chore(deps): update of critical underlying dependencies with go-libp2p to v0.42.0 (filecoin-project/lotus#13190) and boxo to v0.32.0 ([filecoin-project/lotus#13202](https://github.com/filecoin-project/lotus/pull/13202)) and boxo v0.33.0([filecoin-project/lotus#13226](https://github.com/filecoin-project/lotus/pull/13226)) - feat(spcli): correctly handle the batch logic of `lotus-miner actor settle-deal`; replace the dealid data source ([filecoin-project/lotus#13189](https://github.com/filecoin-project/lotus/pull/13189)) - feat(spcli): add `--all-deals` to `lotus-miner actor settle-deal`. By default, only expired deals are processed ([filecoin-project/lotus#13243](https://github.com/filecoin-project/lotus/pull/13243)) +- fix(rpc): add read, write and idle configurable timeouts to gateway. ([filecoin-project/lotus#13327](https://github.com/filecoin-project/lotus/pull/13327)) ## 📝 Changelog