From 234278daa045f1153ab292ed6952806bdf5237b5 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Fri, 19 Jul 2024 14:13:52 +0200 Subject: [PATCH] Configurable timeout for HeaderReader polling Previously if the backend stopped responding the client.HeaderByNumber call inside the timed main loop would never return, meaning that if the HeaderReader was only operating in polling mode, calls to LastHeader would always return the last successfully fetched header until OldHeaderTimeout, and if that was set to a high duration then it would effectively always return the last successfully fetched header and never an error. This would lead to the chain appear to be stuck and not advancing to clients of HeaderReader.LastHeader. This change adds a configurable timeout on this call, defaulting to 5s. Other places in HeaderReader that use LastHeader use the passed-in context which the client can control, so no extra timeout was added for these. --- util/headerreader/header_reader.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/util/headerreader/header_reader.go b/util/headerreader/header_reader.go index 06dfcfbfa8..074d24338e 100644 --- a/util/headerreader/header_reader.go +++ b/util/headerreader/header_reader.go @@ -63,6 +63,7 @@ type Config struct { Enable bool `koanf:"enable"` PollOnly bool `koanf:"poll-only" reload:"hot"` PollInterval time.Duration `koanf:"poll-interval" reload:"hot"` + PollTimeout time.Duration `koanf:"poll-timeout" reload:"hot"` SubscribeErrInterval time.Duration `koanf:"subscribe-err-interval" reload:"hot"` TxTimeout time.Duration `koanf:"tx-timeout" reload:"hot"` OldHeaderTimeout time.Duration `koanf:"old-header-timeout" reload:"hot"` @@ -80,6 +81,7 @@ var DefaultConfig = Config{ Enable: true, PollOnly: false, PollInterval: 15 * time.Second, + PollTimeout: 5 * time.Second, SubscribeErrInterval: 5 * time.Minute, TxTimeout: 5 * time.Minute, OldHeaderTimeout: 5 * time.Minute, @@ -94,6 +96,7 @@ func AddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".poll-only", DefaultConfig.PollOnly, "do not attempt to subscribe to header events") f.Bool(prefix+".use-finality-data", DefaultConfig.UseFinalityData, "use l1 data about finalized/safe blocks") f.Duration(prefix+".poll-interval", DefaultConfig.PollInterval, "interval when polling endpoint") + f.Duration(prefix+".poll-timeout", DefaultConfig.PollTimeout, "timeout when polling endpoint") f.Duration(prefix+".subscribe-err-interval", DefaultConfig.SubscribeErrInterval, "interval for subscribe error") f.Duration(prefix+".tx-timeout", DefaultConfig.TxTimeout, "timeout when waiting for a transaction") f.Duration(prefix+".old-header-timeout", DefaultConfig.OldHeaderTimeout, "warns if the latest l1 block is at least this old") @@ -108,6 +111,7 @@ var TestConfig = Config{ Enable: true, PollOnly: false, PollInterval: time.Millisecond * 10, + PollTimeout: time.Second * 5, TxTimeout: time.Second * 5, OldHeaderTimeout: 5 * time.Minute, UseFinalityData: false, @@ -287,7 +291,9 @@ func (s *HeaderReader) broadcastLoop(ctx context.Context) { s.possiblyBroadcast(h) timer.Stop() case <-timer.C: - h, err := s.client.HeaderByNumber(ctx, nil) + timedCtx, cancelFunc := context.WithTimeout(ctx, s.config().PollTimeout) + h, err := s.client.HeaderByNumber(timedCtx, nil) + cancelFunc() if err != nil { s.setError(fmt.Errorf("failed reading HeaderByNumber: %w", err)) if !errors.Is(err, context.Canceled) {