Go HTTP Request Cancellation for TTS: Propagating Context
Stop unnecessary TTS work after an HTTP client disconnects. Learn how to propagate r.Context, apply context.WithTimeout, verify cancellation, and handle the boundary to a real synthesis engine.
Closing an HTTP connection does not automatically stop a server-side TTS job. To stop work owned by a request, propagate a context derived from r.Context() into the worker and make the worker respond to cancellation. This article shows how to combine request cancellation with a processing deadline in a standard Go net/http server.
Concept illustration of stopping unnecessary speech-synthesis work after a client disconnects. This is not a system architecture diagram.
Why does CPU usage continue after the connection closes?
Consider an API generating 20 seconds of speech when the listener cancels playback. If the server finishes synthesizing the entire result, it spends compute on audio nobody will hear. Two separate steps matter: the server must detect cancellation, and the synthesis job must be able to respond by stopping.
A Go Context communicates that work should stop. Sending a cancellation signal does not forcibly terminate a function or goroutine that is already running. Adding a ctx parameter alone is therefore insufficient. The official Context article describes workers observing Done() and returning.
When is r.Context canceled?
For an incoming server request, the context is canceled when the client connection closes, when an HTTP/2 request is canceled, or when ServeHTTP returns. Do not assume closing a browser page and the server detecting a disconnect happen at the same instant. If a proxy sits in between, check which connection the server actually observes. Official API documentation
Starting work with context.Background() breaks its cancellation relationship with the request context. For a synchronous API whose caller is waiting for the result, use r.Context() as the usual starting point for the job's lifetime.
Combine request cancellation with a timeout
The standalone example below simulates a job taking roughly two seconds. It does not generate audio: the ticker models work stages that can respond to cancellation. The illustrative HTTP processing limit is three seconds.
context.WithTimeout(r.Context(), ...) inherits request cancellation and adds a time limit. Calling cancel() with defer also releases the derived context's resources when work finishes early. Cancellation and timeout guide
A canceled request is logged and the handler returns. If the caller is still connected but this example's processing deadline expires, the handler sends HTTP 504. A streaming service that has already sent response headers or body data cannot choose a new status code this way; it needs its own stream termination path.
Try the behavior locally
Save the code as main.go, then run:
From another terminal, a normal request returns simulation complete after approximately two seconds:
Limit the client's wait to 0.3 seconds and curl exits with a timeout. Once the server detects the disconnect, it logs request canceled: context canceled and stops the simulated work. That log demonstrates observed cancellation; it does not prove that a real engine's GPU work has stopped.
Check the boundary to the real TTS engine
For an external HTTP engine, use a cancellation-aware request such as http.NewRequestWithContext(ctx, ...). However, canceling the caller's wait does not establish that work inside the remote engine has stopped. The remote service must handle cancellation as well.
The same boundary applies to in-process C/C++ libraries and GPU inference. If an engine cannot respond directly to a Go context, connect cancellation to an engine-specific stop mechanism or a safe boundary between chunks. Without such a mechanism, distinguish stopping the caller's wait from reclaiming the computation itself.
A batch job that must complete after the HTTP request ends needs a separately designed lifetime and deadline. This example targets work whose lifetime belongs to a waiting HTTP request.
Checks before applying the pattern
- Check that intermediate functions do not replace the request context with
Background().
- Check that the actual worker observes cancellation and returns.
- Use logs or instrumentation to verify cancellation reaches any external engine.
- Check errors that occur after a streaming response has already started.
Validation scope: four automated checks passed with Go 1.24.7 on Linux amd64: normal response, parent-context cancellation, deadline expiry, and cancellation from a real HTTP client. This does not validate a real TTS engine, GPU, proxy, or HTTP/2 deployment.