Good point. To those unaware, the time.After is equivalent to time.NewTimer(d).C, but "the underlying Timer is not recovered by the garbage collector until the timer fires" (quote from the doc).
That slowAPICall function should look like:
func slowAPICall(ctx context.Context) string {
d := rand.Intn(5)
t := time.NewTimer(time.Duration(d) * time.Second)
defer t.Stop()
...
}
So you have to propagate the Context, hm.
IIRC, go test will panic the test case when it times out. Not exactly sure tho.
It would be nice if there was a kind of 'abort' feature to clean up subroutines spun off this thread
In most cases, at the inner-most level you end up calling some sort of external library (sql, api-client, ...) that will handle the Done() channel itself.
All you have to do is make sure is to pass to the library the context that carries your timeout or cancellation signal. The "rule" that everyone seems to follow is to always take as first argument a context.Context if your library handles cancellation.
The best there is with the context package is to make sure to call the cancel function given to you by contexts that have cancelation. Usually you do this via defer. The cancel function is a no-op if the context is finished otherwise. All this ends up doing though is making sure that things that clean themselves up know to clean themselves up eventually.
I agree this is usually done by defer, but you probably should not do it that way unless your code is very simple. Consider a function body which I've seen variants of many times:
Safe yes, but optimal? process doesn't use the context, and may take longer than the timeout. The context will continue running, with some associated resource cost (at the very least, the context's goroutine and timer). A minimal change is: