Lezioni del modulo (5/5)
`context.Context`: cancellazione e deadline
Il pacchetto context è lo standard de-facto per propagare
cancellazione, deadline e valori scope-bound lungo la catena di
chiamate, attraverso goroutine multiple. Tutti i pacchetti standard
"di rete" (net/http, database/sql, os/exec) accettano un
context.Context come primo parametro.
L'interfaccia
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}Done(): canale chiuso quando il context viene cancellato.Err(): motivo della cancellazione (context.Canceledocontext.DeadlineExceeded).Deadline(): scadenza opzionale.Value(): lookup di valori scope-bound (usare con parsimonia).
Le quattro funzioni di base
ctx := context.Background() // radice: usalo in main, init, test
ctx := context.TODO() // "non so ancora": stub esplicito
ctx, cancel := context.WithCancel(parent) // cancel manuale
ctx, cancel := context.WithTimeout(parent, d) // scadenza relativa
ctx, cancel := context.WithDeadline(parent, t) // scadenza assoluta
ctx := context.WithValue(parent, key, value) // valore scope-boundTutte le forme derivate ritornano una funzione cancel: chiamala
sempre (idealmente con defer) per liberare risorse interne, anche se
il context è già scaduto.
Pattern timeout
func fetchUser(id string) (*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
return queryUser(ctx, id) // queryUser deve rispettare ctx
}Se queryUser impiega più di 2 secondi, ctx.Done() si chiude e la
funzione deve abortire l'operazione.
Rispettare il context: select su Done()
func slowWork(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err() // canceled o deadline exceeded
case <-time.After(100 * time.Millisecond):
// un tick di lavoro
}
}
}La regola: ovunque la tua funzione possa bloccare a lungo, deve gestire ctx.Done(). Altrimenti il context è "spento" inutilmente.
Convenzione del primo parametro
func Fetch(ctx context.Context, url string) ([]byte, error) { ... }
// ^^^^^^^^^^^^^^^^^^^
// SEMPRE primo, mai dentro a una struct, mai opzionalectxSEMPRE come primo parametro nominatoctx.- Mai mettere
Contextcome campo di una struct (eccezioni rarissime). - Mai passare
nil: usacontext.TODO()per stub.
context.WithValue: con parsimonia
type userKey struct{}
ctx := context.WithValue(ctx, userKey{}, currentUser)
// più sotto:
if u, ok := ctx.Value(userKey{}).(*User); ok { ... }Linee guida:
- SOLO per dati scope-request (request ID, trace ID, user identity).
- MAI per parametri di funzione: passali normalmente.
- Chiavi di tipo custom non esportato per evitare collisioni.
Propagazione lungo la catena
func handleHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // context legato al ciclo di vita della richiesta
user, err := fetchUser(ctx, r.URL.Query().Get("id"))
if err != nil { ... }
// ...
}net/http cancella automaticamente r.Context() quando il client
disconnette o quando l'handler termina. Propagandolo a tutte le
chiamate sottostanti, l'intero albero di operazioni viene cancellato
in catena: niente lavoro sprecato dopo la disconnessione.
Prova tu
Crea un context con timeout di 1 secondo derivato da context.Background() e ricorda di chiamare cancel con defer.
Mostra suggerimento
`context.WithTimeout(parent, d)` ritorna `(ctx, cancel)`; chiama sempre cancel.
Soluzione disponibile dopo 3 tentativi
Usa <-ctx.Done() in un select per attendere la cancellazione del context.
Mostra suggerimento
`ctx.Done()` è un channel che si chiude quando il context viene cancellato.
Soluzione disponibile dopo 3 tentativi
Per convenzione, dove va il parametro ctx in una firma di funzione?
func Fetch(???, url string) ([]byte, error)Recap
context.Contextpropaga cancellazione, deadline e valori scope-bound.Background()/TODO()come radice;WithCancel/WithTimeout/WithDeadlineper derivare.- Sempre
defer cancel()per liberare risorse. - Le funzioni "lunghe" devono fare
selectsuctx.Done()o passare ctx ai pacchetti standard. - Convenzione:
ctxcome PRIMO parametro, mai in una struct. WithValuesolo per dati scope-request (request ID, trace ID).- In
net/http,r.Context()è cancellato al disconnect del client.