Vai al contenuto
eLearner.app
Modulo 7 · Lezione 5 di 535/50 nel corso~14 min
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

Go
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.Canceled o context.DeadlineExceeded).
  • Deadline(): scadenza opzionale.
  • Value(): lookup di valori scope-bound (usare con parsimonia).

Le quattro funzioni di base

Go
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-bound

Tutte le forme derivate ritornano una funzione cancel: chiamala sempre (idealmente con defer) per liberare risorse interne, anche se il context è già scaduto.

Pattern timeout

Go
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()

Go
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

Go
func Fetch(ctx context.Context, url string) ([]byte, error) { ... }
//        ^^^^^^^^^^^^^^^^^^^
// SEMPRE primo, mai dentro a una struct, mai opzionale
  • ctx SEMPRE come primo parametro nominato ctx.
  • Mai mettere Context come campo di una struct (eccezioni rarissime).
  • Mai passare nil: usa context.TODO() per stub.

context.WithValue: con parsimonia

Go
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

Go
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

Esercizio#go.m7.l5.e1
Tentativi: 0Caricamento…

Crea un context con timeout di 1 secondo derivato da context.Background() e ricorda di chiamare cancel con defer.

Caricamento editor…
Mostra suggerimento

`context.WithTimeout(parent, d)` ritorna `(ctx, cancel)`; chiama sempre cancel.

Soluzione disponibile dopo 3 tentativi

Esercizio#go.m7.l5.e2
Tentativi: 0Caricamento…

Usa <-ctx.Done() in un select per attendere la cancellazione del context.

Caricamento editor…
Mostra suggerimento

`ctx.Done()` è un channel che si chiude quando il context viene cancellato.

Soluzione disponibile dopo 3 tentativi

Quiz#go.m7.l5.e3
Pronto

Per convenzione, dove va il parametro ctx in una firma di funzione?

Go
func Fetch(???, url string) ([]byte, error)
Opzioni di risposta

Recap

  • context.Context propaga cancellazione, deadline e valori scope-bound.
  • Background()/TODO() come radice; WithCancel/WithTimeout/WithDeadline per derivare.
  • Sempre defer cancel() per liberare risorse.
  • Le funzioni "lunghe" devono fare select su ctx.Done() o passare ctx ai pacchetti standard.
  • Convenzione: ctx come PRIMO parametro, mai in una struct.
  • WithValue solo per dati scope-request (request ID, trace ID).
  • In net/http, r.Context() è cancellato al disconnect del client.