From 28c85965f2c66a1ff4d9b9c50b85d89e20a78785 Mon Sep 17 00:00:00 2001 From: SAY-5 Date: Mon, 20 Apr 2026 05:39:39 -0700 Subject: [PATCH] exactly-once-counter: drain and close HTTP body, back off on retryable failures sendCountRequest hits http.Post in a tight loop and never closes the response body, pinning the underlying TCP connection to the example counter until the garbage collector eventually runs. On error or non-204 responses the loop retries immediately, so the unread bodies pile up faster than GC reclaims them and the example run stalls on FD / connection pressure as soon as the server momentarily returns 4xx/5xx (#664). Drain and close the body on every iteration, and sleep 100ms before retrying both the transport-error path and the non-204 path so we do not hammer the counter handler in a spin loop when the backend is briefly unhealthy. Behaviour on the happy path (204) is unchanged. Fixes #664 --- .../exactly-once-delivery-counter/run.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/_examples/real-world-examples/exactly-once-delivery-counter/run.go b/_examples/real-world-examples/exactly-once-delivery-counter/run.go index cb4ead13d..c70a49210 100644 --- a/_examples/real-world-examples/exactly-once-delivery-counter/run.go +++ b/_examples/real-world-examples/exactly-once-delivery-counter/run.go @@ -3,6 +3,7 @@ package main import ( stdSQL "database/sql" "fmt" + "io" "net/http" "os/exec" "sync" @@ -139,12 +140,26 @@ func sendCountRequest(counterUUID string) { for { resp, err := http.Post("http://localhost:8080/count/"+counterUUID, "", nil) if err != nil { + // No resp to close on transport errors; back off briefly so we + // do not hammer the handler in a tight retry loop. + time.Sleep(100 * time.Millisecond) continue } - if resp.StatusCode == http.StatusNoContent { + status := resp.StatusCode + // Drain and close the body on every attempt. http.Post returns a + // non-nil resp.Body even for non-2xx responses, and skipping the + // drain pins the underlying TCP connection until GC (#664). + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + + if status == http.StatusNoContent { break } + + // Any non-2xx response: back off instead of retrying in a tight + // loop, same rationale as the transport-error branch above. + time.Sleep(100 * time.Millisecond) } }