backend / go
Le package context
Explication
Ce que vous allez apprendre
- Comprendre le rôle de
context.Contextdans une chaîne d'appels concurrents - Utiliser
WithTimeoutetWithDeadlinepour borner la durée d'une opération - Annuler manuellement une opération en cours avec
WithCancel - Lire une annulation avec
ctx.Done()etctx.Err() - Savoir pourquoi
WithValuedoit rester exceptionnel
Dans quel contexte ?
Un utilisateur ferme l'onglet de son navigateur avant qu'une requête HTTP lente ait fini de traiter sa demande côté serveur. Sans mécanisme d'annulation, le serveur continuerait à consommer du CPU et de la mémoire pour un résultat que plus personne n'attend. context.Context est le mécanisme standard de Go pour propager ce signal d'abandon à travers toute une chaîne d'appels, potentiellement sur plusieurs goroutines.
D'abord, un principe simple : ctx circule toujours en premier paramètre
Par convention forte de tout l'écosystème Go, une fonction potentiellement longue ou annulable reçoit un context.Context comme tout premier paramètre, nommé ctx. Cette convention est tellement répandue que go vet et les linters la vérifient automatiquement.
context.Background() est le point de départ standard : un contexte racine, jamais annulé, à partir duquel on dérive des contextes enfants avec des propriétés supplémentaires.
Une fois ce point de départ posé, trois façons de créer un contexte dérivé existent
Chacune répond à un besoin différent de contrôle sur la durée de vie de l'opération.
| Fonction | Déclenchement de l'annulation | Cas d'usage typique |
|---|---|---|
WithTimeout(ctx, duree) | Après une durée relative | Un appel réseau qui ne doit pas dépasser 2 secondes |
WithDeadline(ctx, instant) | À un instant précis | Une opération qui doit finir avant minuit |
WithCancel(ctx) | Manuellement, via la fonction cancel retournée | Arrêter des goroutines suite à une action utilisateur |
Prérequis
Il faut être à l'aise avec select et les channels (leçons précédentes) : ctx.Done() retourne un channel qui se ferme à l'annulation, exactement comme les patterns déjà vus.
Il reste une règle stricte à respecter systématiquement
Chaque fonction WithTimeout/WithDeadline/WithCancel retourne à la fois un contexte et une fonction cancel. Cette fonction cancel DOIT être appelée, généralement via defer annuler() juste après la création du contexte, même si le délai a déjà expiré naturellement.
Piège fréquent
Oublier d'appeler cancel() (même quand le timeout s'est déjà déclenché) provoque une fuite de ressources internes au runtime Go : le contexte et ses éventuels contextes enfants restent en mémoire plus longtemps que nécessaire. C'est un des rares cas de "fuite mémoire" possible en Go malgré le garbage collector, et le linter go vet la signale généralement (lostcancel).
Ensuite, comment une fonction réagit-elle concrètement à l'annulation ?
ctx.Done() retourne un channel qui se ferme au moment de l'annulation (timeout dépassé ou cancel() appelé). ctx.Err() explique ensuite la raison précise : context.DeadlineExceeded ou context.Canceled. Une fonction bien écrite surveille ce channel dans un select, aux côtés de son travail normal.
Bonne pratique
Réserve context.WithValue à la propagation de métadonnées transverses comme un ID de trace ou un utilisateur authentifié — jamais pour faire transiter des paramètres métier normaux d'une fonction. Une fonction ne devrait jamais avoir besoin de fouiller un contexte pour trouver un argument qu'elle pourrait simplement recevoir en paramètre explicite.
Maintenant que tu maîtrises toute la boîte à outils de la concurrence Go, il est temps de revenir à l'organisation du code lui-même à plus grande échelle : comment structurer un vrai projet avec des modules et des packages.
Commandes & code
Le package context
context.Context propage annulation, deadlines et valeurs à travers une chaîne d'appels concurrents.
package main
import (
"context"
"errors"
"fmt"
"time"
)
// Convention Go : ctx est toujours le PREMIER parametre, nomme "ctx"
func appelerAPI(ctx context.Context, id int) (string, error) {
select {
case <-time.After(300 * time.Millisecond): // simule un appel reseau lent
return fmt.Sprintf("donnees-%d", id), nil
case <-ctx.Done():
return "", ctx.Err() // context.Canceled ou context.DeadlineExceeded
}
}
func main() {
// context.Background() : racine, jamais annule, point de depart standard
ctx := context.Background()
// WithTimeout : annule automatiquement apres une duree
ctxTimeout, annuler := context.WithTimeout(ctx, 100*time.Millisecond)
defer annuler() // toujours appeler annuler, meme si le timeout expire deja
_, err := appelerAPI(ctxTimeout, 1)
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("l'appel a depasse le delai imparti")
}
// WithCancel : annulation manuelle, utile pour arreter des goroutines
ctxCancel, annulerManuel := context.WithCancel(ctx)
go func() {
time.Sleep(50 * time.Millisecond)
annulerManuel() // declenche ctx.Done() pour tous les consommateurs
}()
_, err = appelerAPI(ctxCancel, 2)
fmt.Println("erreur attendue:", err)
// WithDeadline : annule a un instant precis (plutot qu'une duree relative)
deadline := time.Now().Add(200 * time.Millisecond)
ctxDeadline, annuler2 := context.WithDeadline(ctx, deadline)
defer annuler2()
// WithValue : propager des valeurs de requete (ID de trace, utilisateur...)
// A UTILISER AVEC PARCIMONIE : pas pour des parametres de fonction normaux
type cleContexte string
const cleRequestID cleContexte = "requestID"
ctxAvecValeur := context.WithValue(ctx, cleRequestID, "req-12345")
traiterRequete := func(ctx context.Context) {
if id, ok := ctx.Value(cleRequestID).(string); ok {
fmt.Println("traitement de la requete:", id)
}
}
traiterRequete(ctxAvecValeur)
_ = ctxDeadline
}Résumé
ctx context.Contextest toujours le premier paramètre d'une fonction qui peut être longue/annulable.WithTimeout/WithDeadline/WithCancelretournent uncontext.Contextfille et une fonctioncancelàdefer.ctx.Done()se ferme à l'annulation ;ctx.Err()explique pourquoi (CanceledouDeadlineExceeded).WithValuesert à propager des métadonnées de requête, jamais des paramètres métier.
Exercices pratiques
Mission : un serveur qui continue de travailler pour des clients partis
Objectif : Propager correctement une annulation de contexte à travers un appel réseau, et diagnostiquer une fuite de ressources liée à un cancel oublié.
Contexte
Le serveur de recherche produit expose GET /recherche qui appelle appelerAPI(ctx, id), un appel réseau externe pouvant prendre jusqu'à 2 secondes. Le monitoring révèle que même après qu'un client ait fermé sa connexion (page fermée), l'appel réseau côté serveur continue de tourner jusqu'à son terme naturel, gaspillant CPU et connexions sortantes.