context.WithoutCancel în Go

Creează un context copil care păstrează valorile părintelui, dar nu se anulează când părintele este anulat.

Sintaxă
func WithoutCancel(parent Context) Context

Ce face context.WithoutCancel

context.WithoutCancel(ctx) returnează un context care vede toate valorile lui ctx (ID de cerere, utilizator), dar se rupe de anularea lui: când ctx este anulat sau expiră, noul context rămâne activ. Nu are termen limită, Err() este mereu nil, iar Done() returnează nil.

Este util pentru operații care trebuie să se termine chiar dacă cererea originală s-a încheiat: scrierea în jurnalul de audit, trimiterea unui e-mail de confirmare după răspuns, curățenie. Pornești operația cu WithoutCancel(r.Context()) și, de obicei, îi pui propria limită: context.WithTimeout(context.WithoutCancel(ctx), 10*time.Second).

Parametri

Parametru Descriere
parent Contextul ale cărui valori se păstrează.

Ce returnează

Un context nou, fără anulare și fără termen limită.

Exemple

Exemplul 1: Părintele se anulează, copilul nu

package main

import (
	"context"
	"fmt"
)

type cheie struct{}

func main() {
	parinte, cancel := context.WithCancel(context.WithValue(context.Background(), cheie{}, "req-42"))
	independent := context.WithoutCancel(parinte)

	cancel()
	<-parinte.Done()
	fmt.Println("Părinte: ", parinte.Err())
	fmt.Println("Copil:   ", independent.Err(), "| Done=nil:", independent.Done() == nil)
	fmt.Println("Valoarea moștenită:", independent.Value(cheie{}))
}

Rezultat

Părinte:  context canceled
Copil:    <nil> | Done=nil: true
Valoarea moștenită: req-42

Exemplul 2: Jurnal de audit scris după încheierea cererii

Cererea are 50 ms, dar scrierea auditului durează 100 ms. Cu WithoutCancel și o limită proprie de o secundă, auditul se termină.

package main

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

func scrieAudit(ctx context.Context, mesaj string) error {
	select {
	case <-time.After(100 * time.Millisecond):
		fmt.Println("Audit salvat:", mesaj)
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

func main() {
	cerere, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
	defer cancel()

	fmt.Println("Cu contextul cererii:", scrieAudit(cerere, "Ana a șters produsul 17"))

	ctxAudit, cancelAudit := context.WithTimeout(context.WithoutCancel(cerere), time.Second)
	defer cancelAudit()
	fmt.Println("Cu WithoutCancel:    ", scrieAudit(ctxAudit, "Ana a șters produsul 17"))
}

Rezultat

Cu contextul cererii: context deadline exceeded
Audit salvat: Ana a șters produsul 17
Cu WithoutCancel:     <nil>

Greșeli frecvente

  • Folosești WithoutCancel fără nicio limită proprie: dacă operația se blochează, nu o mai oprește nimic. Adaugă WithTimeout.
  • Îl folosești ca să „scapi” de anulare în operații care chiar ar trebui oprite când utilizatorul pleacă (interogări de citire): consumi resurse degeaba.

Comenzi înrudite

  • context.WithValue: Creează un context copil care poartă o pereche cheie-valoare (ID de cerere, utilizator autentificat), citită cu ctx.Value(cheie).
  • context.WithTimeout: Creează un context care se anulează singur după durata dată; limita de timp standard pentru cereri și interogări.
  • context.AfterFunc: Programează funcția f să ruleze (într-un goroutine nou) când contextul este anulat; stop() renunță la ea.
  • context.Background: Contextul rădăcină, gol: nu se anulează, nu are termen și nici valori; de la el pornesc toate celelalte contexte.