From dad46a0ea5540cf8300da6b8ea91a7c07a27fe9c Mon Sep 17 00:00:00 2001 From: Michael Kaplan Date: Thu, 6 Nov 2025 18:27:22 -0500 Subject: [PATCH 1/5] Ensure full response body is always read to avoid unnecessary RST_STREAM and PING frames --- api/metrics/client.go | 13 +++++++++++++ tests/fixture/tmpnet/check_monitoring.go | 7 ++++++- tests/fixture/tmpnet/monitor_processes.go | 7 ++++++- tests/fixture/tmpnet/node.go | 7 ++++++- utils/dynamicip/ifconfig_resolver.go | 7 ++++++- utils/rpc/json.go | 13 +++++++++++++ 6 files changed, 50 insertions(+), 4 deletions(-) diff --git a/api/metrics/client.go b/api/metrics/client.go index e00ae1be1aaa..4681dab43a07 100644 --- a/api/metrics/client.go +++ b/api/metrics/client.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "fmt" + "io" "net/http" "net/url" @@ -52,6 +53,10 @@ func (c *Client) GetMetrics(ctx context.Context) (map[string]*dto.MetricFamily, // Return an error for any non successful status code if resp.StatusCode < 200 || resp.StatusCode > 299 { + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) + // Drop any error during close to report the original error _ = resp.Body.Close() return nil, fmt.Errorf("received status code: %d", resp.StatusCode) @@ -60,9 +65,17 @@ func (c *Client) GetMetrics(ctx context.Context) (map[string]*dto.MetricFamily, var parser expfmt.TextParser metrics, err := parser.TextToMetricFamilies(resp.Body) if err != nil { + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) + // Drop any error during close to report the original error _ = resp.Body.Close() return nil, err } + + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) return metrics, resp.Body.Close() } diff --git a/tests/fixture/tmpnet/check_monitoring.go b/tests/fixture/tmpnet/check_monitoring.go index b306d20a1011..ebef511e9a9e 100644 --- a/tests/fixture/tmpnet/check_monitoring.go +++ b/tests/fixture/tmpnet/check_monitoring.go @@ -114,7 +114,12 @@ func queryLoki( if err != nil { return 0, stacktrace.Errorf("failed to execute request: %w", err) } - defer resp.Body.Close() + defer func() { + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() // Read and parse response body, err := io.ReadAll(resp.Body) diff --git a/tests/fixture/tmpnet/monitor_processes.go b/tests/fixture/tmpnet/monitor_processes.go index b0d891a8b7e4..de87b0abfc20 100644 --- a/tests/fixture/tmpnet/monitor_processes.go +++ b/tests/fixture/tmpnet/monitor_processes.go @@ -591,7 +591,12 @@ func checkReadiness(ctx context.Context, url string) (bool, string, error) { if err != nil { return false, "", stacktrace.Errorf("request failed: %w", err) } - defer resp.Body.Close() + defer func() { + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() body, err := io.ReadAll(resp.Body) if err != nil { diff --git a/tests/fixture/tmpnet/node.go b/tests/fixture/tmpnet/node.go index e572cd4bfdfa..86f9687c46a8 100644 --- a/tests/fixture/tmpnet/node.go +++ b/tests/fixture/tmpnet/node.go @@ -224,7 +224,12 @@ func (n *Node) SaveMetricsSnapshot(ctx context.Context) error { if err != nil { return stacktrace.Wrap(err) } - defer resp.Body.Close() + defer func() { + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() body, err := io.ReadAll(resp.Body) if err != nil { return stacktrace.Wrap(err) diff --git a/utils/dynamicip/ifconfig_resolver.go b/utils/dynamicip/ifconfig_resolver.go index dccbbcbdc7a0..a38aa828857d 100644 --- a/utils/dynamicip/ifconfig_resolver.go +++ b/utils/dynamicip/ifconfig_resolver.go @@ -31,7 +31,12 @@ func (r *ifConfigResolver) Resolve(ctx context.Context) (netip.Addr, error) { if err != nil { return netip.Addr{}, err } - defer resp.Body.Close() + defer func() { + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() ipBytes, err := io.ReadAll(resp.Body) if err != nil { diff --git a/utils/rpc/json.go b/utils/rpc/json.go index 62fc90169bbd..95f747829393 100644 --- a/utils/rpc/json.go +++ b/utils/rpc/json.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "fmt" + "io" "net/http" "net/url" @@ -49,15 +50,27 @@ func SendJSONRequest( // Return an error for any non successful status code if resp.StatusCode < 200 || resp.StatusCode > 299 { + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) + // Drop any error during close to report the original error _ = resp.Body.Close() return fmt.Errorf("received status code: %d", resp.StatusCode) } if err := rpc.DecodeClientResponse(resp.Body, reply); err != nil { + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) + // Drop any error during close to report the original error _ = resp.Body.Close() return fmt.Errorf("failed to decode client response: %w", err) } + + // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. + // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive + _, _ = io.Copy(io.Discard, resp.Body) return resp.Body.Close() } From 3b7c18207d2857fa6baafab533ab77b51ec683f0 Mon Sep 17 00:00:00 2001 From: Michael Kaplan Date: Thu, 6 Nov 2025 22:16:48 -0500 Subject: [PATCH 2/5] Close helper --- api/metrics/client.go | 21 ++++++------------ tests/fixture/tmpnet/check_monitoring.go | 8 ++----- tests/fixture/tmpnet/monitor_processes.go | 8 ++----- tests/fixture/tmpnet/node.go | 9 +++----- utils/dynamicip/ifconfig_resolver.go | 8 ++----- utils/rpc/json.go | 26 +++++++++++------------ 6 files changed, 27 insertions(+), 53 deletions(-) diff --git a/api/metrics/client.go b/api/metrics/client.go index 4681dab43a07..ac0c72b4689c 100644 --- a/api/metrics/client.go +++ b/api/metrics/client.go @@ -7,12 +7,13 @@ import ( "bytes" "context" "fmt" - "io" "net/http" "net/url" "github.com/prometheus/common/expfmt" + "github.com/ava-labs/avalanchego/utils/rpc" + dto "github.com/prometheus/client_model/go" ) @@ -46,6 +47,7 @@ func (c *Client) GetMetrics(ctx context.Context) (map[string]*dto.MetricFamily, return nil, fmt.Errorf("failed to create request: %w", err) } + //nolint:bodyclose // Body is closed via rpc.CleanlyCloseBody in all code paths resp, err := http.DefaultClient.Do(request) if err != nil { return nil, fmt.Errorf("failed to issue request: %w", err) @@ -53,29 +55,18 @@ func (c *Client) GetMetrics(ctx context.Context) (map[string]*dto.MetricFamily, // Return an error for any non successful status code if resp.StatusCode < 200 || resp.StatusCode > 299 { - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - // Drop any error during close to report the original error - _ = resp.Body.Close() + _ = rpc.CleanlyCloseBody(resp.Body) return nil, fmt.Errorf("received status code: %d", resp.StatusCode) } var parser expfmt.TextParser metrics, err := parser.TextToMetricFamilies(resp.Body) if err != nil { - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - // Drop any error during close to report the original error - _ = resp.Body.Close() + _ = rpc.CleanlyCloseBody(resp.Body) return nil, err } - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - return metrics, resp.Body.Close() + return metrics, rpc.CleanlyCloseBody(resp.Body) } diff --git a/tests/fixture/tmpnet/check_monitoring.go b/tests/fixture/tmpnet/check_monitoring.go index ebef511e9a9e..0d0d2d587658 100644 --- a/tests/fixture/tmpnet/check_monitoring.go +++ b/tests/fixture/tmpnet/check_monitoring.go @@ -24,6 +24,7 @@ import ( "github.com/ava-labs/avalanchego/tests/fixture/stacktrace" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/rpc" ) type getCountFunc func() (int, error) @@ -114,12 +115,7 @@ func queryLoki( if err != nil { return 0, stacktrace.Errorf("failed to execute request: %w", err) } - defer func() { - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() - }() + defer func() { _ = rpc.CleanlyCloseBody(resp.Body) }() // Read and parse response body, err := io.ReadAll(resp.Body) diff --git a/tests/fixture/tmpnet/monitor_processes.go b/tests/fixture/tmpnet/monitor_processes.go index de87b0abfc20..7a06e04e81a5 100644 --- a/tests/fixture/tmpnet/monitor_processes.go +++ b/tests/fixture/tmpnet/monitor_processes.go @@ -24,6 +24,7 @@ import ( "github.com/ava-labs/avalanchego/tests/fixture/stacktrace" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/perms" + "github.com/ava-labs/avalanchego/utils/rpc" ) const ( @@ -591,12 +592,7 @@ func checkReadiness(ctx context.Context, url string) (bool, string, error) { if err != nil { return false, "", stacktrace.Errorf("request failed: %w", err) } - defer func() { - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() - }() + defer func() { _ = rpc.CleanlyCloseBody(resp.Body) }() body, err := io.ReadAll(resp.Body) if err != nil { diff --git a/tests/fixture/tmpnet/node.go b/tests/fixture/tmpnet/node.go index 86f9687c46a8..554b9b771abc 100644 --- a/tests/fixture/tmpnet/node.go +++ b/tests/fixture/tmpnet/node.go @@ -24,6 +24,7 @@ import ( "github.com/ava-labs/avalanchego/tests/fixture/stacktrace" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" + "github.com/ava-labs/avalanchego/utils/rpc" "github.com/ava-labs/avalanchego/vms/platformvm/signer" ) @@ -224,12 +225,8 @@ func (n *Node) SaveMetricsSnapshot(ctx context.Context) error { if err != nil { return stacktrace.Wrap(err) } - defer func() { - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() - }() + defer func() { _ = rpc.CleanlyCloseBody(resp.Body) }() + body, err := io.ReadAll(resp.Body) if err != nil { return stacktrace.Wrap(err) diff --git a/utils/dynamicip/ifconfig_resolver.go b/utils/dynamicip/ifconfig_resolver.go index a38aa828857d..e8ee7e15104c 100644 --- a/utils/dynamicip/ifconfig_resolver.go +++ b/utils/dynamicip/ifconfig_resolver.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/ava-labs/avalanchego/utils/ips" + "github.com/ava-labs/avalanchego/utils/rpc" ) var _ Resolver = (*ifConfigResolver)(nil) @@ -31,12 +32,7 @@ func (r *ifConfigResolver) Resolve(ctx context.Context) (netip.Addr, error) { if err != nil { return netip.Addr{}, err } - defer func() { - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() - }() + defer func() { _ = rpc.CleanlyCloseBody(resp.Body) }() ipBytes, err := io.ReadAll(resp.Body) if err != nil { diff --git a/utils/rpc/json.go b/utils/rpc/json.go index 95f747829393..1b30aa98e411 100644 --- a/utils/rpc/json.go +++ b/utils/rpc/json.go @@ -14,6 +14,14 @@ import ( rpc "github.com/gorilla/rpc/v2/json2" ) +// CleanlyCloseBody avoids sending unnecessary RST_STREAM and PING frames by ensuring +// the whole body is read before being closed. +// See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive +func CleanlyCloseBody(body io.ReadCloser) error { + _, _ = io.Copy(io.Discard, body) + return body.Close() +} + func SendJSONRequest( ctx context.Context, uri *url.URL, @@ -43,6 +51,7 @@ func SendJSONRequest( request.Header = ops.headers request.Header.Set("Content-Type", "application/json") + //nolint:bodyclose // body is closed via CleanlyCloseBody in all code paths resp, err := http.DefaultClient.Do(request) if err != nil { return fmt.Errorf("failed to issue request: %w", err) @@ -50,27 +59,16 @@ func SendJSONRequest( // Return an error for any non successful status code if resp.StatusCode < 200 || resp.StatusCode > 299 { - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - // Drop any error during close to report the original error - _ = resp.Body.Close() + _ = CleanlyCloseBody(resp.Body) return fmt.Errorf("received status code: %d", resp.StatusCode) } if err := rpc.DecodeClientResponse(resp.Body, reply); err != nil { - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - // Drop any error during close to report the original error - _ = resp.Body.Close() + _ = CleanlyCloseBody(resp.Body) return fmt.Errorf("failed to decode client response: %w", err) } - // Avoid sending unnecessary RST_STREAM and PING frames by ensuring the whole body is read. - // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive - _, _ = io.Copy(io.Discard, resp.Body) - return resp.Body.Close() + return CleanlyCloseBody(resp.Body) } From 5e980d759b14c5dcf8d143cc45f30d722353091c Mon Sep 17 00:00:00 2001 From: Michael Kaplan Date: Thu, 6 Nov 2025 22:20:26 -0500 Subject: [PATCH 3/5] nit --- api/metrics/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/metrics/client.go b/api/metrics/client.go index ac0c72b4689c..cbaff955bcfe 100644 --- a/api/metrics/client.go +++ b/api/metrics/client.go @@ -47,7 +47,7 @@ func (c *Client) GetMetrics(ctx context.Context) (map[string]*dto.MetricFamily, return nil, fmt.Errorf("failed to create request: %w", err) } - //nolint:bodyclose // Body is closed via rpc.CleanlyCloseBody in all code paths + //nolint:bodyclose // body is closed via rpc.CleanlyCloseBody in all code paths resp, err := http.DefaultClient.Do(request) if err != nil { return nil, fmt.Errorf("failed to issue request: %w", err) From b3e5936a78ce1bc6e81637db16a9c08899a2825c Mon Sep 17 00:00:00 2001 From: Michael Kaplan Date: Fri, 7 Nov 2025 12:07:38 -0500 Subject: [PATCH 4/5] Always ignore body.Close() errors --- api/metrics/client.go | 9 +++------ tests/fixture/tmpnet/check_monitoring.go | 3 ++- tests/fixture/tmpnet/monitor_processes.go | 3 ++- tests/fixture/tmpnet/node.go | 3 ++- utils/dynamicip/ifconfig_resolver.go | 3 ++- utils/rpc/json.go | 17 +++++++---------- 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/api/metrics/client.go b/api/metrics/client.go index cbaff955bcfe..045aaea2bcf5 100644 --- a/api/metrics/client.go +++ b/api/metrics/client.go @@ -47,26 +47,23 @@ func (c *Client) GetMetrics(ctx context.Context) (map[string]*dto.MetricFamily, return nil, fmt.Errorf("failed to create request: %w", err) } - //nolint:bodyclose // body is closed via rpc.CleanlyCloseBody in all code paths + //nolint:bodyclose // body is closed via rpc.CleanlyCloseBody resp, err := http.DefaultClient.Do(request) if err != nil { return nil, fmt.Errorf("failed to issue request: %w", err) } + defer rpc.CleanlyCloseBody(resp.Body) // Return an error for any non successful status code if resp.StatusCode < 200 || resp.StatusCode > 299 { - // Drop any error during close to report the original error - _ = rpc.CleanlyCloseBody(resp.Body) return nil, fmt.Errorf("received status code: %d", resp.StatusCode) } var parser expfmt.TextParser metrics, err := parser.TextToMetricFamilies(resp.Body) if err != nil { - // Drop any error during close to report the original error - _ = rpc.CleanlyCloseBody(resp.Body) return nil, err } - return metrics, rpc.CleanlyCloseBody(resp.Body) + return metrics, nil } diff --git a/tests/fixture/tmpnet/check_monitoring.go b/tests/fixture/tmpnet/check_monitoring.go index 0d0d2d587658..65d213c0942a 100644 --- a/tests/fixture/tmpnet/check_monitoring.go +++ b/tests/fixture/tmpnet/check_monitoring.go @@ -111,11 +111,12 @@ func queryLoki( req.Header.Set("Authorization", "Basic "+auth) // Execute request + //nolint:bodyclose // body is closed via rpc.CleanlyCloseBody resp, err := http.DefaultClient.Do(req) if err != nil { return 0, stacktrace.Errorf("failed to execute request: %w", err) } - defer func() { _ = rpc.CleanlyCloseBody(resp.Body) }() + defer rpc.CleanlyCloseBody(resp.Body) // Read and parse response body, err := io.ReadAll(resp.Body) diff --git a/tests/fixture/tmpnet/monitor_processes.go b/tests/fixture/tmpnet/monitor_processes.go index 7a06e04e81a5..547ba9264203 100644 --- a/tests/fixture/tmpnet/monitor_processes.go +++ b/tests/fixture/tmpnet/monitor_processes.go @@ -588,11 +588,12 @@ func checkReadiness(ctx context.Context, url string) (bool, string, error) { return false, "", stacktrace.Wrap(err) } + //nolint:bodyclose // body is closed via rpc.CleanlyCloseBody resp, err := http.DefaultClient.Do(req) if err != nil { return false, "", stacktrace.Errorf("request failed: %w", err) } - defer func() { _ = rpc.CleanlyCloseBody(resp.Body) }() + defer rpc.CleanlyCloseBody(resp.Body) body, err := io.ReadAll(resp.Body) if err != nil { diff --git a/tests/fixture/tmpnet/node.go b/tests/fixture/tmpnet/node.go index 554b9b771abc..8a5563cc6b0f 100644 --- a/tests/fixture/tmpnet/node.go +++ b/tests/fixture/tmpnet/node.go @@ -221,11 +221,12 @@ func (n *Node) SaveMetricsSnapshot(ctx context.Context) error { if err != nil { return stacktrace.Wrap(err) } + //nolint:bodyclose // body is closed via rpc.CleanlyCloseBody resp, err := http.DefaultClient.Do(req) if err != nil { return stacktrace.Wrap(err) } - defer func() { _ = rpc.CleanlyCloseBody(resp.Body) }() + defer rpc.CleanlyCloseBody(resp.Body) body, err := io.ReadAll(resp.Body) if err != nil { diff --git a/utils/dynamicip/ifconfig_resolver.go b/utils/dynamicip/ifconfig_resolver.go index e8ee7e15104c..e1a83a7db6b9 100644 --- a/utils/dynamicip/ifconfig_resolver.go +++ b/utils/dynamicip/ifconfig_resolver.go @@ -28,11 +28,12 @@ func (r *ifConfigResolver) Resolve(ctx context.Context) (netip.Addr, error) { return netip.Addr{}, err } + //nolint:bodyclose // body is closed via rpc.CleanlyCloseBody resp, err := http.DefaultClient.Do(req) if err != nil { return netip.Addr{}, err } - defer func() { _ = rpc.CleanlyCloseBody(resp.Body) }() + defer rpc.CleanlyCloseBody(resp.Body) ipBytes, err := io.ReadAll(resp.Body) if err != nil { diff --git a/utils/rpc/json.go b/utils/rpc/json.go index 1b30aa98e411..2c5f129abca3 100644 --- a/utils/rpc/json.go +++ b/utils/rpc/json.go @@ -14,12 +14,12 @@ import ( rpc "github.com/gorilla/rpc/v2/json2" ) -// CleanlyCloseBody avoids sending unnecessary RST_STREAM and PING frames by ensuring -// the whole body is read before being closed. +// CleanlyCloseBody avoids sending unnecessary RST_STREAM and PING frames by +// ensuring the whole body is read before being closed. // See https://blog.cloudflare.com/go-and-enhance-your-calm/#reading-bodies-in-go-can-be-unintuitive -func CleanlyCloseBody(body io.ReadCloser) error { +func CleanlyCloseBody(body io.ReadCloser) { _, _ = io.Copy(io.Discard, body) - return body.Close() + _ = body.Close() } func SendJSONRequest( @@ -51,24 +51,21 @@ func SendJSONRequest( request.Header = ops.headers request.Header.Set("Content-Type", "application/json") - //nolint:bodyclose // body is closed via CleanlyCloseBody in all code paths + //nolint:bodyclose // body is closed via CleanlyCloseBody resp, err := http.DefaultClient.Do(request) if err != nil { return fmt.Errorf("failed to issue request: %w", err) } + defer CleanlyCloseBody(resp.Body) // Return an error for any non successful status code if resp.StatusCode < 200 || resp.StatusCode > 299 { - // Drop any error during close to report the original error - _ = CleanlyCloseBody(resp.Body) return fmt.Errorf("received status code: %d", resp.StatusCode) } if err := rpc.DecodeClientResponse(resp.Body, reply); err != nil { - // Drop any error during close to report the original error - _ = CleanlyCloseBody(resp.Body) return fmt.Errorf("failed to decode client response: %w", err) } - return CleanlyCloseBody(resp.Body) + return nil } From ead4b627c7a3debf07020b90725c338f41fcb967 Mon Sep 17 00:00:00 2001 From: Michael Kaplan Date: Fri, 7 Nov 2025 15:27:44 -0500 Subject: [PATCH 5/5] simplify return Co-authored-by: Stephen Buttolph --- api/metrics/client.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/api/metrics/client.go b/api/metrics/client.go index 045aaea2bcf5..e744461f65e0 100644 --- a/api/metrics/client.go +++ b/api/metrics/client.go @@ -60,10 +60,5 @@ func (c *Client) GetMetrics(ctx context.Context) (map[string]*dto.MetricFamily, } var parser expfmt.TextParser - metrics, err := parser.TextToMetricFamilies(resp.Body) - if err != nil { - return nil, err - } - - return metrics, nil + return parser.TextToMetricFamilies(resp.Body) }