# Context en Go: Cancelación, Timeouts y Valores de Request-Scope

En los posts anteriores exploramos las **goroutines**, **channels** y el `select` statement. Hoy vamos a descubrir una de las características más importantes de Go para escribir código concurrente robusto: el `context` package. Si vienes de Java, `context` es similar a `CompletableFuture.cancel()` o `ExecutorService.shutdownNow()`, pero mucho más poderoso e integrado.

## **¿Qué es Context?**

El `context` package proporciona una forma estándar de manejar:

* ✅ **Cancelación**: Cancelar operaciones en cascada
    
* ✅ **Timeouts**: Limitar el tiempo de ejecución
    
* ✅ **Deadlines**: Tiempos límite absolutos
    
* ✅ **Valores**: Pasar datos de request-scope (user IDs, trace IDs, etc.)
    

### **Comparación: Java vs Go**

| Aspecto | Java | Go |
| --- | --- | --- |
| Cancelación | `Future.cancel()`, `ExecutorService.shutdown()` | `context.WithCancel()` |
| Timeouts | `Future.get(timeout, unit)` | `context.WithTimeout()` |
| Propagación | Manual (pasando flags) | Automática (pasando context) |
| Valores | ThreadLocal (problemático) | Context values (seguro) |
| Integración | Librerías externas | Estándar del lenguaje |

## **Context Básico: Background y TODO**

Go proporciona dos context base:

```go
// context.Background() - Context raíz, nunca se cancela
ctx := context.Background()

// context.TODO() - Placeholder cuando no estás seguro qué usar
ctx := context.TODO()
```

**Cuándo usar cada uno:**

* `Background()`: Context principal de tu aplicación (main, handlers HTTP, etc.)
    
* `TODO()`: Cuando no estás seguro qué context usar (útil durante desarrollo)
    

## **Context con Timeout**

Uno de los casos de uso más comunes es limitar el tiempo de ejecución de una operación:

```go
// Crear context con timeout de 2 segundos
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()  // ← Siempre llama cancel() para liberar recursos

// Simular operación que puede tardar
done := make(chan bool)
go func() {
    time.Sleep(3 * time.Second) // Tarda más que el timeout
    done <- true
}()

select {
case <-done:
    fmt.Println("Operation completed")
case <-ctx.Done():
    fmt.Printf("Operation cancelled: %v\n", ctx.Err())
    // ctx.Err() retorna context.DeadlineExceeded
}
```

**Características importantes:**

* ✅ `cancel()` debe llamarse siempre para liberar recursos
    
* ✅ Usa `defer cancel()` para asegurar que se llame
    
* ✅ `ctx.Done()` retorna un channel que se cierra cuando el context se cancela
    
* ✅ `ctx.Err()` retorna el error (`context.DeadlineExceeded` o `context.Canceled`)
    

### **Verificar si un Context está Cancelado**

```go
if ctx.Err() != nil {
    return ctx.Err()  // Ya está cancelado
}
```

## **Context con Cancelación Manual**

A veces necesitas cancelar una operación manualmente, no por timeout:

```go
ctx, cancel := context.WithCancel(context.Background())

// Goroutine que hace trabajo
go func() {
    for i := 0; i < 10; i++ {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker cancelled: %v\n", ctx.Err())
            return
        default:
            fmt.Printf("Working... %d\n", i)
            time.Sleep(200 * time.Millisecond)
        }
    }
}()

// Cancelar después de 1 segundo
time.Sleep(1 * time.Second)
fmt.Println("Cancelling context...")
cancel()  // ← Cancela el context y todas sus operaciones
time.Sleep(500 * time.Millisecond)
```

**Casos de uso:**

* ✅ Cancelar cuando el usuario presiona "Cancelar"
    
* ✅ Cancelar cuando ocurre un error y no necesitas continuar
    
* ✅ Cancelar operaciones en cascada cuando una falla
    

## **Context con Deadline**

Similar a timeout, pero con un tiempo absoluto:

```go
deadline := time.Now().Add(2 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()

select {
case <-time.After(3 * time.Second):
    fmt.Println("Operation completed")
case <-ctx.Done():
    fmt.Printf("Deadline exceeded: %v\n", ctx.Err())
    // ctx.Err() retorna context.DeadlineExceeded
}
```

**Diferencia entre Timeout y Deadline:**

* `WithTimeout`: Tiempo relativo desde ahora
    
* `WithDeadline`: Tiempo absoluto específico
    

**Cuándo usar cada uno:**

* `WithTimeout`: "Esta operación debe completarse en 5 segundos"
    
* `WithDeadline`: "Esta operación debe completarse antes de las 3:00 PM"
    

## **Context con Valores**

Los context pueden llevar valores que se propagan a través de la cadena de llamadas. Esto es útil para request IDs, user IDs, trace IDs, etc.

```go
// Crear context con valores
ctx := context.WithValue(context.Background(), "userID", 123)
ctx = context.WithValue(ctx, "requestID", "req-456")
ctx = context.WithValue(ctx, "traceID", "trace-789")

// Función que usa los valores
processRequest := func(ctx context.Context) {
    userID := ctx.Value("userID").(int)
    requestID := ctx.Value("requestID").(string)
    traceID := ctx.Value("traceID").(string)

    fmt.Printf("Processing request:\n")
    fmt.Printf("  User ID: %d\n", userID)
    fmt.Printf("  Request ID: %s\n", requestID)
    fmt.Printf("  Trace ID: %s\n", traceID)
}

processRequest(ctx)
```

### **⚠️ Mejores Prácticas para Valores**

**1\. Usa tipos específicos para las keys:**

```go
// ✅ BIEN: Tipo específico para evitar colisiones
type contextKey string

const (
    userIDKey contextKey = "userID"
    requestIDKey contextKey = "requestID"
)

ctx := context.WithValue(context.Background(), userIDKey, 123)
userID := ctx.Value(userIDKey).(int)

// ❌ MAL: Strings pueden colisionar
ctx := context.WithValue(context.Background(), "userID", 123)
```

**2\. Solo usa valores para datos de request-scope:**

```go
// ✅ BIEN: Request-scope data
ctx = context.WithValue(ctx, "userID", userID)
ctx = context.WithValue(ctx, "requestID", requestID)

// ❌ MAL: No uses context para pasar parámetros de función
func processUser(ctx context.Context, userID int) {  // ← userID como parámetro
    // ...
}
```

**3\. Los valores deben ser inmutables:**

```go
// ✅ BIEN: Valores primitivos o inmutables
ctx = context.WithValue(ctx, "userID", 123)
ctx = context.WithValue(ctx, "requestID", "req-123")

// ❌ MAL: No uses estructuras mutables
type Config struct {
    APIKey string
}
ctx = context.WithValue(ctx, "config", &Config{APIKey: "secret"})  // ← Puede mutarse
```

## **Propagando Context en Funciones**

Una de las reglas más importantes en Go: **siempre pasa** `context.Context` como primer parámetro en funciones que pueden cancelarse o necesitan valores del context.

### **Convención de Nombres**

```go
// ✅ BIEN: Context como primer parámetro
func longRunningOperation(ctx context.Context, duration time.Duration) error {
    // ...
}

func fetchData(ctx context.Context, url string) ([]byte, error) {
    // ...
}

func processUser(ctx context.Context, userID int) error {
    // ...
}
```

### **Ejemplo: Operación con Checks Periódicos**

```go
func longRunningOperation(ctx context.Context, duration time.Duration) error {
    fmt.Printf("Starting operation (will take %v)...\n", duration)

    // Verificar si ya está cancelado
    if ctx.Err() != nil {
        return ctx.Err()
    }

    // Simular trabajo con checks periódicos de cancelación
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop()

    start := time.Now()
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Operation cancelled after %v\n", time.Since(start))
            return ctx.Err()
        case <-ticker.C:
            elapsed := time.Since(start)
            fmt.Printf("  Still working... (%v elapsed)\n", elapsed)
            if elapsed >= duration {
                fmt.Printf("Operation completed in %v\n", elapsed)
                return nil
            }
        }
    }
}

// Uso
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

if err := longRunningOperation(ctx, 3*time.Second); err != nil {
    fmt.Printf("Error: %v\n", err)
}
```

**Puntos clave:**

* ✅ Verifica `ctx.Done()` periódicamente en loops largos
    
* ✅ Retorna `ctx.Err()` cuando detectas cancelación
    
* ✅ Usa `select` para no bloquear innecesariamente
    

## **Context en HTTP Requests**

El paquete `net/http` integra context directamente. Cada request tiene un context que puedes usar:

```go
type HTTPClient struct{}

func (c *HTTPClient) Get(ctx context.Context, url string) (string, error) {
    // Simular request HTTP con cancelación
    select {
    case <-ctx.Done():
        return "", ctx.Err()
    case <-time.After(1 * time.Second):
        return fmt.Sprintf("Response from %s", url), nil
    }
}

// Uso
client := &HTTPClient{}
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()

result, err := client.Get(ctx, "https://api.example.com/data")
if err != nil {
    fmt.Printf("Request failed: %v\n", err)
} else {
    fmt.Printf("Success: %s\n", result)
}
```

### **Context en HTTP Handlers**

En handlers HTTP, el context viene del request:

```go
func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()  // ← Context del request
    
    // Agregar valores al context
    ctx = context.WithValue(ctx, "userID", getUserID(r))
    
    // Pasar a funciones que necesitan el context
    if err := processRequest(ctx); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.Write([]byte("Success"))
}
```

**Características del HTTP Context:**

* ✅ Se cancela automáticamente cuando el cliente cierra la conexión
    
* ✅ Puedes agregar valores (user ID, request ID, etc.)
    
* ✅ Puedes crear timeouts específicos para operaciones dentro del handler
    

## **Context en Operaciones de Base de Datos**

La mayoría de drivers de base de datos en Go aceptan context:

```go
type Database struct{}

func (db *Database) Query(ctx context.Context, query string) ([]string, error) {
    // Simular query que puede tardar
    select {
    case <-ctx.Done():
        return nil, ctx.Err()
    case <-time.After(2 * time.Second):
        return []string{"result1", "result2", "result3"}, nil
    }
}

// Uso
db := &Database{}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

results, err := db.Query(ctx, "SELECT * FROM users")
if err != nil {
    fmt.Printf("Query failed: %v\n", err)
} else {
    fmt.Printf("Results: %v\n", results)
}
```

### **Ejemplo Real con database/sql**

```go
import (
    "context"
    "database/sql"
    "time"
)

func getUser(ctx context.Context, db *sql.DB, userID int) (*User, error) {
    // Crear context con timeout para la query
    queryCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    
    var user User
    err := db.QueryRowContext(queryCtx, 
        "SELECT id, name, email FROM users WHERE id = $1", userID).
        Scan(&user.ID, &user.Name, &user.Email)
    
    if err != nil {
        return nil, err
    }
    
    return &user, nil
}
```

## **Context Chaining: Encadenar Contexts**

Puedes encadenar contexts para agregar más información o timeouts:

```go
// Context base con valores
baseCtx := context.WithValue(context.Background(), "userID", 123)

// Agregar timeout
timeoutCtx, cancel1 := context.WithTimeout(baseCtx, 2*time.Second)
defer cancel1()

// Agregar más valores
finalCtx := context.WithValue(timeoutCtx, "requestID", "req-999")

// Usar el context final
fmt.Printf("User ID: %d\n", finalCtx.Value("userID"))
fmt.Printf("Request ID: %s\n", finalCtx.Value("requestID"))

// El timeout también está presente
select {
case <-time.After(3 * time.Second):
    fmt.Println("Operation completed")
case <-finalCtx.Done():
    fmt.Printf("Timeout: %v\n", finalCtx.Err())
}
```

**Características del chaining:**

* ✅ Los valores se propagan hacia abajo
    
* ✅ Los timeouts/deadlines se heredan (el más corto gana)
    
* ✅ La cancelación se propaga hacia abajo
    

## **Ejemplo Práctico: Servicio con Múltiples Operaciones**

Un ejemplo completo de cómo usar context en un servicio real:

```go
type Service struct {
    db     *Database
    client *HTTPClient
}

func NewService() *Service {
    return &Service{
        db:     &Database{},
        client: &HTTPClient{},
    }
}

func (s *Service) ProcessData(ctx context.Context, userID int) error {
    // Agregar userID al context
    ctx = context.WithValue(ctx, "userID", userID)

    // Hacer múltiples operaciones con el mismo context
    // Si alguna falla o se cancela, todas se cancelan

    // 1. Query database
    results, err := s.db.Query(ctx, "SELECT * FROM data")
    if err != nil {
        return fmt.Errorf("database query failed: %w", err)
    }

    // 2. Fetch external data
    externalData, err := s.client.Get(ctx, "https://api.example.com/data")
    if err != nil {
        return fmt.Errorf("external API call failed: %w", err)
    }

    fmt.Printf("Processed data: %v, External: %s\n", results, externalData)
    return nil
}

// Uso
service := NewService()

// Context con timeout para toda la operación
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

if err := service.ProcessData(ctx, 123); err != nil {
    fmt.Printf("Error: %v\n", err)
}
```

**Ventajas:**

* ✅ Si la query de DB falla, el context se cancela y la llamada HTTP también puede cancelarse
    
* ✅ Si el timeout se alcanza, ambas operaciones se cancelan
    
* ✅ Los valores (userID) están disponibles en todas las funciones
    

## **Context en Worker Pools**

Usar context para cancelar workers cuando sea necesario:

```go
func workerPoolWithContext(ctx context.Context, numWorkers int) {
    jobs := make(chan Task, 10)
    results := make(chan Result, 10)
    
    // Workers con cancelación
    var wg sync.WaitGroup
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go func(workerID int) {
            defer wg.Done()
            for {
                select {
                case <-ctx.Done():
                    fmt.Printf("Worker %d: Cancelled\n", workerID)
                    return
                case task, ok := <-jobs:
                    if !ok {
                        return
                    }
                    // Procesar tarea
                    result := processTask(ctx, task)
                    
                    select {
                    case results <- result:
                    case <-ctx.Done():
                        return
                    }
                }
            }
        }(w)
    }
    
    // Enviar trabajos con cancelación
    go func() {
        defer close(jobs)
        for i := 1; i <= 100; i++ {
            select {
            case jobs <- Task{ID: i}:
            case <-ctx.Done():
                return
            }
        }
    }()
    
    // Cerrar results cuando terminen
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // Recibir resultados
    for result := range results {
        fmt.Printf("Result: %v\n", result)
    }
}
```

## **Mejores Prácticas**

### **1\. Siempre Pasa Context como Primer Parámetro**

```go
// ✅ BIEN
func processData(ctx context.Context, data []byte) error {
    // ...
}

// ❌ MAL
func processData(data []byte, ctx context.Context) error {
    // ...
}
```

### **2\. Siempre Llama cancel() con defer**

```go
// ✅ BIEN
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()  // ← Siempre libera recursos

// ❌ MAL
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// Olvidar cancel() puede causar memory leaks
```

### **3\. No Almacenes Context en Structs**

```go
// ❌ MAL: Context en struct
type Service struct {
    ctx context.Context  // ← No hagas esto
}

// ✅ BIEN: Pasa context como parámetro
type Service struct {
    db *Database
}

func (s *Service) Process(ctx context.Context, data Data) error {
    // ...
}
```

**Razón**: Los context son específicos de request y no deben compartirse entre requests.

### **4\. Usa Tipos Específicos para Context Keys**

```go
// ✅ BIEN
type contextKey string

const userIDKey contextKey = "userID"

ctx := context.WithValue(ctx, userIDKey, 123)
userID := ctx.Value(userIDKey).(int)

// ❌ MAL: Strings pueden colisionar
ctx := context.WithValue(ctx, "userID", 123)
```

### **5\. Verifica Cancelación en Loops Largos**

```go
// ✅ BIEN: Verificar cancelación periódicamente
for i := 0; i < 1000000; i++ {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // Procesar
    }
}

// ❌ MAL: Loop largo sin verificar cancelación
for i := 0; i < 1000000; i++ {
    // Procesar sin verificar ctx.Done()
}
```

### **6\. No Pases nil Context**

```go
// ❌ MAL
func process(ctx context.Context) {
    if ctx == nil {
        ctx = context.Background()
    }
    // ...
}

// ✅ BIEN: Si es opcional, usa context.Background() como default
func process(ctx context.Context) {
    if ctx == nil {
        ctx = context.Background()
    }
    // O mejor: siempre requiere context
}
```

## **Errores Comunes**

### **❌ Error 1: Olvidar Llamar cancel()**

```go
// ❌ MAL: Memory leak
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// Olvidamos cancel()

// ✅ BIEN: Siempre usar defer
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
```

### **❌ Error 2: Almacenar Context en Structs**

```go
// ❌ MAL: Context compartido entre requests
type Service struct {
    ctx context.Context
}

func NewService(ctx context.Context) *Service {
    return &Service{ctx: ctx}  // ← No hagas esto
}

// ✅ BIEN: Pasar context como parámetro
type Service struct {
    db *Database
}

func (s *Service) Process(ctx context.Context, data Data) error {
    // ...
}
```

### **❌ Error 3: No Verificar Cancelación en Loops**

```go
// ❌ MAL: No puede cancelarse
func processLargeDataset(ctx context.Context, data []Item) {
    for _, item := range data {
        processItem(item)  // ← No verifica ctx.Done()
    }
}

// ✅ BIEN: Verificar periódicamente
func processLargeDataset(ctx context.Context, data []Item) error {
    for i, item := range data {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            processItem(item)
        }
        
        // O verificar cada N iteraciones
        if i%1000 == 0 {
            if ctx.Err() != nil {
                return ctx.Err()
            }
        }
    }
    return nil
}
```

### **❌ Error 4: Usar Context Values para Parámetros de Función**

```go
// ❌ MAL: Context no es para pasar parámetros
func getUser(ctx context.Context) (*User, error) {
    userID := ctx.Value("userID").(int)  // ← No hagas esto
    // ...
}

// ✅ BIEN: Parámetros explícitos
func getUser(ctx context.Context, userID int) (*User, error) {
    // ...
}
```

**Regla**: Context values son para datos de request-scope (trace IDs, request IDs), no para parámetros de función.

### **❌ Error 5: Crear Context sin Background o TODO**

```go
// ❌ MAL: No crear context desde nil
var ctx context.Context  // nil
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)  // ← Panic!

// ✅ BIEN: Siempre empezar con Background o TODO
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
```

## **Comparación: Java vs Go**

### **Java: Cancelación Manual**

```java
// Java
ExecutorService executor = Executors.newFixedThreadPool(10);
Future<String> future = executor.submit(() -> {
    // Trabajo largo
    return "result";
});

// Cancelar después de timeout
try {
    String result = future.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    future.cancel(true);
}

// Problemas:
// - No hay propagación automática
// - No hay valores de request-scope
// - Requiere manejo manual de excepciones
```

### **Go: Context**

```go
// Go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

result, err := longOperation(ctx)
if err != nil {
    // Manejar error (puede ser cancelación o timeout)
}

// Ventajas:
// - Propagación automática
// - Valores de request-scope
// - Integrado en el lenguaje
// - Funciona con todas las operaciones
```

## **Context y Select: Combinación Poderosa**

El context y el select trabajan perfectamente juntos:

```go
func processWithMultipleSources(ctx context.Context) error {
    ch1 := make(chan Result)
    ch2 := make(chan Result)
    
    go fetchFromSource1(ctx, ch1)
    go fetchFromSource2(ctx, ch2)
    
    select {
    case result := <-ch1:
        return processResult(result)
    case result := <-ch2:
        return processResult(result)
    case <-ctx.Done():
        return ctx.Err()  // Timeout o cancelación
    }
}
```

## **Conclusiones**

El `context` package es esencial para escribir código concurrente robusto en Go:

✅ **Cancelación en cascada**: Cancela operaciones hijas automáticamente

✅ **Timeouts y deadlines**: Limita el tiempo de ejecución de forma elegante

✅ **Valores de request-scope**: Pasa datos de forma segura sin ThreadLocal

✅ **Integración estándar**: Funciona con HTTP, databases, y todas las librerías estándar

✅ **Propagación automática**: Pasa el context y todo funciona

✅ **Type-safe**: Previene errores en tiempo de compilación

Si vienes de Java, el context puede parecer diferente al principio, pero una vez que entiendas cómo funciona y lo uses consistentemente, verás cómo simplifica enormemente el manejo de cancelación, timeouts y valores de request-scope en aplicaciones concurrentes.

## **Próximos Pasos**

En el siguiente post exploraremos los **patrones de sincronización** en Go usando el paquete `sync`, incluyendo `WaitGroup`, `Mutex`, `RWMutex`, y otros primitivos de sincronización que complementan perfectamente las goroutines, channels y context.

---

**¿Has usado** `context` en tus proyectos de Go? ¿Qué patrones te han resultado más útiles? Comparte tus experiencias y casos de uso en los comentarios. Y si quieres ver el código completo de estos ejemplos, puedes encontrarlo en mi repositorio [go-mastery-lab](https://github.com/joedayz/go-mastery-lab).
