What is wrong with this handler? What error will the goroutine see?
Question 276HardGo 1.22 to 1.25
func handler(w http.ResponseWriter, r *http.Request) {
go func() {
// sends an audit event to another service
if err := sendAudit(r.Context(), r.URL.Path); err != nil {
log.Println("audit:", err)
}
}()
w.WriteHeader(http.StatusAccepted)
}
The request context is canceled when ServeHTTP returns. The handler returns almost immediately, so the goroutine's outgoing HTTP call usually fails with context canceled, sometimes before it even dials. The bug is timing-dependent: it may pass locally and then fail under load, or the other way round.
Two more problems:
- Touching
rafter the handler returns is not allowed (the server may reuse parts of it), and readingr.Bodyafter return is definitely broken. Copy what you need first. - Nothing bounds or tracks the goroutine: it has no timeout and no shutdown coordination.
func handler(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path // copy needed data
ctx, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), 3*time.Second)
go func() {
defer cancel()
if err := sendAudit(ctx, path); err != nil {
log.Println("audit:", err)
}
}()
w.WriteHeader(http.StatusAccepted)
}
In production, prefer a bounded worker pool or queue that drains during graceful shutdown over one goroutine per request.
More on Context
- Q274How do you make a blocking call that doesn't accept a context (e.g. net.Conn.Read) cancelable?
- Q275What does context.WithoutCancel do and when would you use it?
- Q277When exactly is an http.Request's context canceled on the server side, and how do you use it correctly?
- Q278On the HTTP client side, how does context interact with http.Client.Timeout and response bodies?
- Q279How does context cancellation work with database/sql, including transactions and rows?
- Q280Write a function that runs a blocking call with a context and returns early on cancellation. What goroutine leak must you avoid?