context.Context est le câble qui descend de couche en couche. Premier argument, jamais stocké, toujours propagé.context.Context is the cable threading down layer by layer. First argument, never stored, always propagated.
Les volumes précédents ont couvert comment une fonction gère sa propre sortie — defer, capture, recover. Mais un programme concurrent réel n'est pas une fonction : c'est un arbre de goroutines lancé pour une même tâche. Un handler HTTP appelle un service, qui interroge une base, qui démarre deux goroutines de cache. Quand le client raccroche — ou que le délai expire — tout ce travail doit cesser. Personne ne peut appeler individuellement chaque goroutine. Il faut un signal qui descend : context.Context est exactement ce signal. Un contexte est une valeur immuable qui forme un arbre : context.Background() est la racine, et chaque dérivé — WithCancel, WithTimeout, WithValue — crée un nœud fils. Quand un nœud est annulé, tous ses descendants le savent immédiatement via leur canal Done(). Le contexte n'est pas un objet global, une variable d'environnement, ni un champ de struct : c'est le premier argument de chaque fonction qui fait du travail annulable. La convention Go est stricte : ctx context.Context, toujours en tête de signature, toujours passé explicitement. Ce qui semble verbeux est en réalité une propriété de sécurité : chaque site d'appel voit qu'il participe à une annulation, et choisit consciemment quel contexte transmettre.Previous volumes covered how a function manages its own exit — defer, capture, recover. But a real concurrent program isn't a function: it's a tree of goroutines launched for one task. An HTTP handler calls a service, which queries a database, which starts two cache goroutines. When the client hangs up — or the deadline expires — all that work must cease. Nobody can call each goroutine individually. You need a signal that flows down: context.Context is exactly that signal. A context is an immutable value forming a tree: context.Background() is the root, and each derivative — WithCancel, WithTimeout, WithValue — creates a child node. When a node is canceled, all its descendants know immediately via their Done() channel. A context is not a global object, an environment variable, or a struct field: it's the first argument of every function doing cancelable work. The Go convention is strict: ctx context.Context, always first in the signature, always passed explicitly. What seems verbose is actually a safety property: every call site sees that it participates in a cancellation, and consciously chooses which context to forward.
// Le câble traverse les couches : on ne le stocke jamais, on le passe. func handleSearch(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // racine : le contexte de la requête HTTP results, err := search(ctx, r.URL.Query().Get("q")) // … } func search(ctx context.Context, query string) ([]Result, error) { rows, err := db.QueryContext(ctx, "SELECT …", query) // ctx descend jusqu'à SQL if err != nil { return nil, err } // … }
Annuler un nœud parent propage le signal à tous ses descendants. Jamais l'inverse. Le contexte descend, l'annulation descend.Canceling a parent node propagates the signal to all its descendants. Never the reverse. The context flows down, cancellation flows down.
« Est-ce que cette fonction fait du travail qui peut durer ? Si oui, elle accepte un ctx en premier argument et le passe à ses sous-appels. » context.Context n'est pas une option — c'est le fil conducteur de toute opération annulable, de la requête HTTP jusqu'au dernier appel SQL. Une fonction qui l'ignore isole une partie du travail du signal d'annulation — et cette partie continuera à tourner même après que tout le reste s'est arrêté."Does this function do work that can last? If yes, it takes a ctx as first argument and passes it to its sub-calls." context.Context is not an option — it's the conductor of every cancelable operation, from the HTTP request down to the last SQL call. A function that ignores it isolates part of the work from the cancellation signal — and that part will keep running even after everything else has stopped.
Un contexte ne se modifie jamais en place. WithCancel, WithTimeout et WithValue créent chacun un nouveau contexte-fils qui pointe vers son parent. Cette immuabilité garantit qu'un fils ne peut pas altérer son parent : la couche service peut restreindre son délai sans risquer d'annuler le contexte du handler. L'arbre ne peut être élagué que depuis la racine vers les feuilles, jamais l'inverse. C'est ce qui rend la propagation sûre dans un programme concurrent : plusieurs goroutines peuvent lire le même contexte simultanément, sans synchronisation, car rien ne l'écrit.A context never mutates in place. WithCancel, WithTimeout, and WithValue each create a new child context pointing to its parent. This immutability guarantees a child can't alter its parent: the service layer can tighten its deadline without risking canceling the handler's context. The tree can only be pruned from root toward leaves, never the reverse. That's what makes propagation safe in a concurrent program: multiple goroutines can read the same context simultaneously, without synchronization, because nothing writes to it.