DMarlon Studio

Lección 16 · 16 de 20

context: tiempo límite y cancelación · Go

Ponga un tiempo límite a una operación y cancélela con context en Go: WithTimeout, WithCancel, ctx.Done() y ctx.Err() con un proveedor lento.

Go · context: tiempo límite y cancelación en DMarlon Studio: el código del ejemplo y su salida en la terminal

Un `context.Context` viaja de función en función y lleva una orden: «deja lo que estés haciendo». Por convención es el primer parámetro y se llama ctx, como en fetchQuote(ctx, supplier, delay). Las bibliotecas de red y de bases de datos de Go lo aceptan en casi todas sus funciones.

fetchQuote simula un proveedor que tarda delay en dar precio. Su select espera dos cosas: que pase ese tiempo o que se cierre `ctx.Done()`, un canal que se cierra cuando el contexto vence o se cancela. Lo que llegue antes decide. `ctx.Err()` explica el motivo y se envuelve con %w para no perderlo.

`context.WithTimeout` crea un contexto que vence solo, aquí a los 100 ms. FastParts contesta en 10 ms y da su precio; SlowSupply tardaría un segundo, así que gana el plazo y el error es context deadline exceeded. errors.Is(err, context.DeadlineExceeded) lo reconoce aunque esté envuelto.

WithTimeout también devuelve una función cancel, y hay que llamarla siempre, aunque el plazo no llegue a vencer, para liberar sus recursos. Normalmente se escribe defer cancel(); dentro de un bucle se llama al final de cada vuelta, porque un defer esperaría a que terminase main.

`context.WithCancel` no vence solo: se cancela al llamar a cancel(), por ejemplo cuando el cliente cierra la página. Aquí se cancela antes de pedir el precio, así que ctx.Done() ya está cerrado y el error es context canceled. Los tiempos (10, 100 y 1000 ms) están tan separados que la salida es siempre la misma.

Código · main.go

package mainimport (	"context"	"errors"	"fmt"	"time")// fetchQuote simulates a supplier that answers after delay, unless ctx ends first.func fetchQuote(ctx context.Context, supplier string, delay time.Duration) (string, error) {	select {	case <-time.After(delay):		return supplier + " quotes 42.50 EUR", nil	case <-ctx.Done(): // closed on timeout or on cancel		return "", fmt.Errorf("%s: %w", supplier, ctx.Err())	}}func main() {	delays := map[string]time.Duration{"FastParts": 10 * time.Millisecond, "SlowSupply": time.Second}	for _, supplier := range []string{"FastParts", "SlowSupply"} {		ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)		quote, err := fetchQuote(ctx, supplier, delays[supplier])		cancel() // always release the context, even if it was not needed		fmt.Printf("%-28q err=%v timeout=%t\n", quote, err, errors.Is(err, context.DeadlineExceeded))	}	ctx, cancel := context.WithCancel(context.Background())	cancel() // e.g. the customer closed the page before we asked	_, err := fetchQuote(ctx, "FastParts", 10*time.Millisecond)	fmt.Println("err:", err, "| canceled:", errors.Is(err, context.Canceled))}

Salida · go run main.go

"FastParts quotes 42.50 EUR" err=<nil> timeout=false
""                           err=SlowSupply: context deadline exceeded timeout=true
err: FastParts: context canceled | canceled: true

Para recordar

  • ctx va siempre como primer parámetro.
  • ctx.Done() se cierra al vencer el plazo o al cancelar.
  • ctx.Err() dice por qué: DeadlineExceeded o Canceled.
  • Llame siempre a cancel(), normalmente con defer.
  • errors.Is reconoce el motivo aunque el error esté envuelto.

Ejercicio

Pida los dos precios a la vez, cada uno en su goroutine y con el mismo contexto de 100 ms, guarde cada resultado en su posición de un slice y escríbalos en orden cuando terminen los dos.