From 91611a289ea2c053b764061b131edf3b3d7add99 Mon Sep 17 00:00:00 2001 From: dongjiang1989 Date: Mon, 18 Sep 2023 20:51:20 +0800 Subject: [PATCH 1/2] allow configuration of PingTimeout/ReadIdleTimeout Signed-off-by: dongjiang1989 --- config/http_config.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/config/http_config.go b/config/http_config.go index 4763549b..97782393 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -52,6 +52,18 @@ var ( // 5 minutes is typically above the maximum sane scrape interval. So we can // use keepalive for all configurations. idleConnTimeout: 5 * time.Minute, + // readIdleTimeout is the timeout after which a health check using ping + // frame will be carried out if no frame is received on the connection. + // Note that a ping response will is considered a received frame, so if + // there is no other traffic on the connection, the health check will + // be performed every readIdleTimeout interval. + // If zero, no health check is performed. + // Defaults to 1min. + readIdleTimeout: 1 * time.Minute, + // PingTimeout is the timeout after which the connection will be closed + // if a response to Ping is not received. + // Defaults to 15s. + pingTimeout: 15 * time.Second, } ) @@ -429,6 +441,8 @@ type httpClientOptions struct { keepAlivesEnabled bool http2Enabled bool idleConnTimeout time.Duration + readIdleTimeout time.Duration + pingTimeout time.Duration userAgent string } @@ -470,6 +484,22 @@ func WithUserAgent(ua string) HTTPClientOption { } } +// WithReadIdleTimeout allows setting the health check will +// be performed every HTTP2 readIdleTimeout interval . +func WithReadIdleTimeout(timeout time.Duration) HTTPClientOption { + return func(opts *httpClientOptions) { + opts.readIdleTimeout = timeout + } +} + +// WithPingTimeout allows setting the timeout after which the connection +// will be closed if a response to Ping is not received. +func WithPingTimeout(timeout time.Duration) HTTPClientOption { + return func(opts *httpClientOptions) { + opts.pingTimeout = timeout + } +} + // NewClient returns a http.Client using the specified http.RoundTripper. func newClient(rt http.RoundTripper) *http.Client { return &http.Client{Transport: rt} @@ -541,7 +571,8 @@ func NewRoundTripperFromConfig(cfg HTTPClientConfig, name string, optFuncs ...HT if err != nil { return nil, err } - http2t.ReadIdleTimeout = time.Minute + http2t.ReadIdleTimeout = opts.readIdleTimeout + http2t.PingTimeout = opts.pingTimeout } // If a authorization_credentials is provided, create a round tripper that will set the From 0575daa5792cd4d93be53a50c2284d4eacdd5be1 Mon Sep 17 00:00:00 2001 From: dongjiang1989 Date: Tue, 19 Sep 2023 10:00:56 +0800 Subject: [PATCH 2/2] add unittest case Signed-off-by: dongjiang1989 --- config/http_config_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/config/http_config_test.go b/config/http_config_test.go index 4ebe038b..f9260d02 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -580,6 +580,34 @@ func TestCustomIdleConnTimeout(t *testing.T) { } } +func TestPingTimeoutAndReadIdleTimeout(t *testing.T) { + clientDone := make(chan struct{}) + pingTimeout := time.Millisecond * 10 + readIdleTimeout := time.Millisecond * 50 + + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + <-clientDone + })) + ts.Start() + defer ts.Close() + + cfg := HTTPClientConfig{} + rt, err := NewRoundTripperFromConfig(cfg, "test", WithPingTimeout(pingTimeout), WithReadIdleTimeout(readIdleTimeout)) + if err != nil { + t.Fatalf("Can't create a round-tripper from this config: %+v", cfg) + } + + ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) + defer cancel() + + defer close(clientDone) + req, _ := http.NewRequestWithContext(ctx, "GET", ts.URL, nil) + _, err = rt.RoundTrip(req) + if err == nil || !strings.Contains(err.Error(), "context deadline exceeded") { + t.Fatalf("expected to get error about \"deadline exceeded\", got %+v", err) + } +} + func TestMissingBearerAuthFile(t *testing.T) { cfg := HTTPClientConfig{ BearerTokenFile: MissingBearerTokenFile,