Go

Why must you always call the CancelFunc, even if the operation finished successfully? What leaks if you don't?

Question 261MediumGo 1.22 to 1.25

Deriving a cancelable context registers the child in its parent's children map. WithTimeout/WithDeadline also start a time.Timer, and a custom parent may cause a watcher goroutine. Calling cancel frees all of these. If you skip it, the child stays reachable from the parent (and the timer stays armed) until the parent is canceled or the deadline fires. With a long-lived parent such as Background() or a server's base context, that is a real memory leak that grows with each request.

func fetch(ctx context.Context, url string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel() // ALWAYS, right after creation

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

go vet's lostcancel check flags code paths where the cancel function is dropped. Calling cancel more than once is safe (it is idempotent), so defer cancel() plus an early explicit cancel() is fine.

Gotcha: defer cancel() inside a long loop postpones every cancel until the function returns. Put the loop body in its own function, or call cancel() at the end of each iteration.

More on Context

All 35 Context questions