context.WithTimeout în Go

Creează un context care se anulează singur după durata dată; limita de timp standard pentru cereri și interogări.

Sintaxă
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

Ce face context.WithTimeout

context.WithTimeout(parinte, 2*time.Second) creează un context care expiră automat după 2 secunde. La expirare, ctx.Done() se închide și ctx.Err() devine context.DeadlineExceeded. Este echivalent cu WithDeadline(parinte, time.Now().Add(timeout)).

Funcțiile din biblioteca standard care primesc un context îl respectă: http.NewRequestWithContext, interogările database/sql, exec.CommandContext. Pui deci limita o singură dată și ea se aplică întregului lanț de apeluri.

Dacă părintele are deja un termen mai apropiat, rămâne acela: un copil nu poate primi mai mult timp decât părintele. Ca la WithCancel, apelează întotdeauna cancel (cu defer), ca să eliberezi temporizatorul dacă operația termină mai devreme.

Parametri

Parametru Descriere
parent Contextul părinte.
timeout Cât timp are operația la dispoziție (time.Duration).

Ce returnează

Contextul cu limită de timp și funcția cancel.

Exemple

Exemplul 1: Interogare prea lentă

Funcția interogare simulează o bază de date. Cu limita de 100 ms, prima cerere reușește, a doua nu.

package main

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

func interogare(ctx context.Context, sql string, durata time.Duration) (string, error) {
	select {
	case <-time.After(durata):
		return "rezultat pentru " + sql, nil
	case <-ctx.Done():
		return "", ctx.Err()
	}
}

func main() {
	for _, durata := range []time.Duration{20 * time.Millisecond, 500 * time.Millisecond} {
		ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
		rez, err := interogare(ctx, "SELECT * FROM comenzi", durata)
		cancel()

		if errors.Is(err, context.DeadlineExceeded) {
			fmt.Println("Interogarea a depășit limita:", err)
			continue
		}
		fmt.Println(rez)
	}
}

Rezultat

rezultat pentru SELECT * FROM comenzi
Interogarea a depășit limita: context deadline exceeded

Exemplul 2: Copilul nu primește mai mult timp decât părintele

package main

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

func main() {
	parinte, cancel1 := context.WithTimeout(context.Background(), 50*time.Millisecond)
	defer cancel1()
	copil, cancel2 := context.WithTimeout(parinte, time.Hour)
	defer cancel2()

	dp, _ := parinte.Deadline()
	dc, _ := copil.Deadline()
	fmt.Println("Același termen ca părintele:", dp.Equal(dc))

	<-copil.Done()
	fmt.Println("Copilul a expirat:", copil.Err())
}

Rezultat

Același termen ca părintele: true
Copilul a expirat: context deadline exceeded

Exemplul 3: Cerere HTTP cu limită de timp

Contextul se dă cererii; dacă serverul nu răspunde în 3 secunde, Do returnează eroarea. Exemplul are nevoie de internet.

package main

import (
	"context"
	"fmt"
	"net/http"
	"time"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, "GET", "https://dexblog.ro/", nil)
	if err != nil {
		fmt.Println(err)
		return
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Println("eroare:", err)
		return
	}
	defer resp.Body.Close()
	fmt.Println("Status:", resp.Status)
}

Rezultat posibil (diferă la fiecare rulare)

Status: 200 OK

Greșeli frecvente

  • Creezi contextul cu limită în afara unei bucle și îl folosești pentru toate iterațiile: limita se aplică întregii bucle, nu fiecărei cereri. Creează câte unul pe iterație și apelează cancel la sfârșitul fiecăreia.
  • Pui defer cancel() într-o buclă lungă: apelurile se adună până la ieșirea din funcție. În buclă apelează cancel() direct, ca în primul exemplu.
  • Compari eroarea cu == după ce a fost împachetată de alte funcții; folosește errors.Is(err, context.DeadlineExceeded).

Comenzi înrudite

  • context.WithDeadline: Creează un context care se anulează singur la un moment fix (ora limită), nu după o durată.
  • context.WithTimeoutCause: Ca WithTimeout și WithDeadline, dar la expirare context.Cause(ctx) returnează eroarea ta, nu doar DeadlineExceeded.
  • context.WithCancel: Creează un context copil și o funcție cancel; apelul cancel() anunță toate goroutine-urile care îl folosesc să se oprească.
  • time.After: Returnează un canal care primește ora curentă după durata dată; folosit în select pentru limite de timp (timeout).
  • errors.Is: Verifică dacă eroarea err este target sau o conține, căutând prin toate erorile împachetate cu %w sau Join.