context.Context în Go

Interfața care duce prin program semnalul de anulare, termenul limită și valori legate de o cerere (ctx).

Sintaxă
import "context"

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}

func Background() Context
func TODO() Context
func WithValue(parent Context, key, val any) Context

Ce face context.Context

Un context.Context însoțește o operație (o cerere HTTP, o interogare, o sarcină de fundal) prin toate funcțiile pe care le apelează. Prin el se transmit trei lucruri: anularea (utilizatorul a închis pagina, deci nu mai are rost să continui), termenul limită (operația trebuie să se termine în 2 secunde) și, rar, valori legate de cerere (ID-ul cererii).

Metodele: Done() returnează un canal care se închide când contextul este anulat sau expiră; îl folosești în select. Err() spune de ce: nil cât timp e activ, apoi context.Canceled sau context.DeadlineExceeded. Deadline() dă termenul limită, dacă există, iar Value(cheie) citește o valoare pusă cu context.WithValue.

Contextele formează un arbore: pornești de la context.Background() (rădăcina, folosită în main și în teste) și creezi copii cu WithCancel, WithTimeout, WithDeadline sau WithValue. Anularea unui părinte anulează toți copiii. context.TODO() este tot un context gol, pus temporar când nu știi încă de unde să iei contextul. Prin convenție, contextul este primul parametru al funcției și se numește ctx.

Ce returnează

Background, TODO și WithValue returnează un Context. Contextul rădăcină nu este anulat niciodată și nu are termen limită.

Exemple

Exemplul 1: O funcție care respectă anularea

Bucla verifică la fiecare pas canalul ctx.Done(). Când contextul expiră, funcția se oprește și returnează ctx.Err().

package main

import (
	"context"
	"fmt"
	"time"
)

func proceseazaComenzi(ctx context.Context, comenzi []string) error {
	for _, c := range comenzi {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-time.After(60 * time.Millisecond): // procesarea unei comenzi
			fmt.Println("Procesat:", c)
		}
	}
	return nil
}

func main() {
	comenzi := []string{"#101", "#102", "#103", "#104", "#105"}

	err := proceseazaComenzi(context.Background(), comenzi[:2])
	fmt.Println("Fără limită, eroare:", err)

	ctx, cancel := context.WithTimeout(context.Background(), 150*time.Millisecond)
	defer cancel()
	err = proceseazaComenzi(ctx, comenzi)
	fmt.Println("Cu limită de 150ms, eroare:", err)
}

Rezultat

Procesat: #101
Procesat: #102
Fără limită, eroare: <nil>
Procesat: #101
Procesat: #102
Cu limită de 150ms, eroare: context deadline exceeded

Exemplul 2: Valori în context și metodele interfeței

Cheia pentru WithValue are un tip propriu (cheieCerere), ca să nu se ciocnească cu cheile altor pachete.

package main

import (
	"context"
	"fmt"
	"time"
)

type cheieCerere struct{}

func logheaza(ctx context.Context, mesaj string) {
	id, _ := ctx.Value(cheieCerere{}).(string)
	fmt.Printf("[%s] %s\n", id, mesaj)
}

func main() {
	ctx := context.WithValue(context.Background(), cheieCerere{}, "cerere-42")
	logheaza(ctx, "încep plata")

	_, are := ctx.Deadline()
	fmt.Println("Are termen limită:", are, "- Err:", ctx.Err())

	termen := time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)
	ctx2, cancel := context.WithDeadline(ctx, termen)
	d, are := ctx2.Deadline()
	fmt.Println("Copilul are termen:", are, d.Format(time.DateOnly))
	logheaza(ctx2, "valoarea se moștenește de la părinte")
	cancel()
	<-ctx2.Done()
	fmt.Println("După cancel, Err:", ctx2.Err())
}

Rezultat

[cerere-42] încep plata
Are termen limită: false - Err: <nil>
Copilul are termen: true 2030-01-01
[cerere-42] valoarea se moștenește de la părinte
După cancel, Err: context canceled

Greșeli frecvente

  • Păstrezi contextul într-un câmp de structură pentru mai târziu. Contextul ține de o singură operație; transmite-l ca prim parametru fiecărei funcții.
  • Dai nil ca context. Funcțiile se opresc cu panică; folosește context.Background() sau context.TODO().
  • Pui prin WithValue parametri obișnuiți ai funcției (utilizatorul, limita). Valorile din context sunt pentru date care traversează API-uri (ID de cerere, date de autentificare), nu pentru parametri opționali.
  • Folosești un string simplu ca cheie pentru WithValue; alt pachet poate folosi același text. Definește un tip propriu pentru chei.

Comenzi înrudite

  • context.Background: Contextul rădăcină, gol: nu se anulează, nu are termen și nici valori; de la el pornesc toate celelalte contexte.
  • context.WithValue: Creează un context copil care poartă o pereche cheie-valoare (ID de cerere, utilizator autentificat), citită cu ctx.Value(cheie).
  • context.WithCancel: Creează un context copil și o funcție cancel; apelul cancel() anunță toate goroutine-urile care îl folosesc să se oprească.
  • context.WithTimeout: Creează un context care se anulează singur după durata dată; limita de timp standard pentru cereri și interogări.
  • context.WithDeadline: Creează un context care se anulează singur la un moment fix (ora limită), nu după o durată.
  • context.WithCancelCause: Ca WithCancel, dar cancel primește motivul anulării; context.Cause(ctx) returnează acel motiv.
  • context.WithoutCancel: Creează un context copil care păstrează valorile părintelui, dar nu se anulează când părintele este anulat.
  • context.AfterFunc: Programează funcția f să ruleze (într-un goroutine nou) când contextul este anulat; stop() renunță la ea.