Control de contexto en Go

Los desarrolladores de Go debaten cómo usar `context` y goroutines de forma responsable, con foco en la cancelación, la vida útil de los recursos y en evitar concurrencia oculta en las bibliotecas. Muchos prefieren APIs que parezcan síncronas y donde los llamadores controlen si el trabajo se ejecuta en paralelo, aunque reconocen casos legítimos para goroutines internas y trabajadores de larga duración. La conversación también critica `context` como una mezcla de cancelación y datos acotados al pedido, lo compara con patrones en Rust y C#, y menciona la concurrencia estructurada y los límites del ideal de Go de “sin coloración async”.

Goroutines en bibliotecas y diseño de API

  • Tema fuerte: las APIs de bibliotecas deberían parecer síncronas; los llamadores deciden si quieren concurrencia creando ellos mismos goroutines.
  • Las goroutines internas son aceptables cuando:
    • Forman claramente parte de la abstracción (por ejemplo, rastreador web, map paralelo, pub/sub por lotes, expulsor de métricas).
    • Están acotadas, bien documentadas y son ajustables (límites, tasa, loteo, topes de trabajo en curso).
    • Se limpian correctamente antes de que la función pública devuelva el control (estilo de concurrencia estructurada, a menudo mediante errgroup o WaitGroup).
  • Quienes critican “nunca inicies goroutines en bibliotecas” dicen que eso es demasiado rígido; algunas cargas de trabajo son más simples y seguras si la biblioteca se encarga de la planificación.
  • Las panics en goroutines en segundo plano son una preocupación: los llamadores no pueden envolverlas en recover a menos que controlen el límite de la goroutine.

Context: propósito, patrón y puntos de dolor

  • Dos roles principales:
    • Cancelación / plazos / “ya puedes parar”.
    • Datos acotados al pedido (logger, trazado, autenticación, etc.).
  • A muchos no les gusta el aspecto de “bolsa de cosas”; es difícil saber por qué una función necesita un context o qué contiene.
  • Otros defienden los parámetros explícitos ctx:
    • Hacen visibles la interrumpibilidad y los requisitos de registro.
    • Son más fáciles de revisar y razonar que el almacenamiento implícito por hilo.
  • Guía debatida:
    • Regla común: no almacenar contexts; pasarlos hacia abajo en la pila.
    • Matiz: está bien en structs que, en la práctica, son parámetros, y en la stdlib por compatibilidad hacia atrás (por ejemplo, net/http), a veces usando NewRequestWithContext.
    • context.WithoutCancel se ve como un compromiso útil cuando se almacenan “valores pero no plazos”.
  • Puntos de fricción:
    • No hay forma estática de saber si los callee respetan el context.
    • Presión para propagar ctx por “casi todas las funciones”, especialmente para el registro.

Context vs. coloración de funciones / modelos async

  • Algunos ven ctx como una ligera “coloración” (funciones con y sin context), socavando en parte la historia de Go de “sin palabra clave async”.
  • Otros sostienen que es mucho menos restrictivo que async/await:
    • Siempre puedes usar context.Background() o envolverlo con timeouts.
    • No crea la separación dura entre sync y async que se ve en otros lenguajes.

Manejo de errores y valores cero (tema lateral)

  • Debate sobre el patrón (T, error):
    • Un bando: los valores cero/nil deberían ser “útiles” incluso cuando error está establecido.
    • Otro: en la mayoría de las API reales, los valores cero no significan nada o son peligrosos; la falta de tipos suma obliga a convenciones incómodas.