WithTimeout et WithDeadline posent une limite de temps à un arbre entier. Le defer cancel() n'est pas une option.WithTimeout and WithDeadline put a time limit on a whole tree. The defer cancel() is not optional.
Le numéro précédent a posé context.Context comme câble d'annulation explicite. Ce numéro ajoute la dimension temporelle : comment poser une limite de temps à tout un arbre de goroutines. context.WithTimeout et context.WithDeadline créent tous les deux un contexte-fils qui s'annule automatiquement à une échéance — WithTimeout exprime une durée relative (dans 5 secondes), WithDeadline exprime une heure absolue (à 14h30:00). Sous le capot, WithTimeout est simplement WithDeadline(ctx, time.Now().Add(d)) : les deux produisent exactement le même contexte. La différence n'est qu'expressive — durée vs instant — et le bon choix dépend de ce qu'on connaît au moment de l'appel. Ce qui les unit, c'est leur second retour : une fonction cancel. Cette fonction cancel sert à deux choses simultanées. Primo, elle annule le contexte-fils avant son échéance si on finit plus tôt — économiser le timer. Secundo, et c'est la raison pour laquelle elle n'est jamais optionnelle : si on ne l'appelle pas, Go maintient un timer du runtime et des entrées dans l'arbre des contextes jusqu'à ce que le parent s'annule. C'est une fuite discrète, invisible sous faible charge, catastrophique à l'échelle. Le defer cancel() en ligne 2 est donc la règle absolue : créer, différer l'annulation, s'en occuper jamais plus.The previous issue laid context.Context as an explicit cancellation cable. This issue adds the temporal dimension: how to put a time limit on a whole goroutine tree. context.WithTimeout and context.WithDeadline both create a child context that auto-cancels at a deadline — WithTimeout expresses a relative duration (in 5 seconds), WithDeadline expresses an absolute time (at 2:30pm). Under the hood, WithTimeout is simply WithDeadline(ctx, time.Now().Add(d)): both produce exactly the same context. The difference is only expressive — duration vs instant — and the right choice depends on what you know at the call site. What unites them is their second return value: a cancel function. This cancel function serves two simultaneous purposes. First, it cancels the child context before its deadline if you finish sooner — saves the timer. Second, and this is why it's never optional: if you don't call it, Go maintains a runtime timer and entries in the context tree until the parent cancels. It's a discreet leak, invisible under light load, catastrophic at scale. The defer cancel() on line 2 is therefore the absolute rule: create, defer the cancellation, never think about it again.
// WithTimeout : dans combien de temps ? ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() // libère le timer même si on finit avant // WithDeadline : à quel moment absolu ? deadline := time.Now().Add(5 * time.Second) ctx, cancel := context.WithDeadline(ctx, deadline) defer cancel() // Les deux produisent le même contexte — c'est l'expression qui diffère. // ctx.Deadline() retourne l'heure d'expiration dans les deux cas.
Le délai descend dans l'arbre via le contexte. Le cancel() remonte dans le code via le defer — toujours en deuxième ligne après WithTimeout.The deadline flows down the tree via the context. The cancel() comes back in code via defer — always the second line after WithTimeout.
« Ai-je créé un contexte avec timeout ou deadline ? Alors la ligne suivante est defer cancel(). » Sans exception — même si on sait que le contexte sera annulé par le parent avant l'échéance, parce que defer cancel() sur un contexte déjà annulé est un no-op, et qu'omettre un defer cancel() sur un contexte vivant est une fuite silencieuse. La règle vaut dans les tests comme en production."Did I create a context with timeout or deadline? Then the next line is defer cancel()." No exception — even if you know the context will be canceled by the parent before the deadline, because defer cancel() on an already-canceled context is a no-op, and omitting a defer cancel() on a live context is a silent leak. The rule holds in tests as in production.
WithTimeout convient quand on connaît une durée — « cette requête SQL ne doit pas dépasser 3 secondes ». WithDeadline convient quand on a une heure cible déjà calculée — un budget de délai partagé entre plusieurs sous-appels, ou une coordination avec une horloge externe. En pratique, WithTimeout couvre 90 % des cas parce que les délais sont le plus souvent relatifs au moment de l'appel. Mais si la couche supérieure a déjà calculé une deadline absolue et la passe en paramètre, préférer WithDeadline évite de la recalculer en durée.WithTimeout suits when you know a duration — 'this SQL query must not exceed 3 seconds.' WithDeadline suits when you have an already-computed target time — a deadline budget shared across several sub-calls, or coordination with an external clock. In practice, WithTimeout covers 90% of cases because deadlines are most often relative to the call moment. But if the upper layer has already computed an absolute deadline and passes it as a parameter, preferring WithDeadline avoids recomputing it as a duration.