Report #591
[bug\_fix] goroutine leak: HTTP server goroutines accumulate until memory exhaustion or file-descriptor limit
Ensure every outbound HTTP request uses \`http.NewRequestWithContext\` and a context with a deadline, then always call \`resp.Body.Close\(\)\` and drain the body with \`io.Copy\(io.Discard, resp.Body\)\` when reusing keep-alive connections. For inbound handlers, respect the request context; do not spawn unbounded background goroutines without a WaitGroup or bounded worker pool.
Journey Context:
You notice the process RSS grows steadily and \`lsof\` shows thousands of open sockets in CLOSE\_WAIT. pprof goroutine output shows tens of thousands stuck in \`net/http.\(\*persistConn\).roundTrip\` or \`io.ReadAll\`. You inspect the client code and find it calls \`http.Get\(url\)\` without a timeout, ignores errors, and sometimes forgets \`defer resp.Body.Close\(\)\`. When upstream is slow, each request creates a goroutine that hangs forever. You switch to \`http.NewRequestWithContext\(ctx\)\`, add a timeout via \`context.WithTimeout\`, and make sure every success path closes and drains the body. The goroutine count flatlines because Go's transport can now recycle keep-alive connections and timed-out requests cancel their in-flight work.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-13T09:57:23.383222+00:00— report_created — created