Skip to content

Concurrencia

Goroutines por instancia

  • 1 goroutine por instancia dentro de whatsmeow para su WebSocket.
  • Goroutines compartidas:
  • usage.Tracker.loop — flush periódico cada 60s.
  • banwatch.loop — evaluator cada 30s.
  • outbox.drainLoop — drainer cada 5s.
  • outbox.expireLoop — expirer cada 30s.
  • 1 goroutine por lifecycle webhook outbound (no bloquea).

Mutexes

Mutex Protege
manager.mu (RWMutex) instances map
manager.reconMu disconnectNotified, pendingReconnected
manager.unreachMu pendingUnreachable
SpamguardTracker.mu historial last-2 + counter de bloqueos
banwatch.mu buckets de eventos send
usage.mu buckets pendientes de flush
outbox.mu serializa el drainer (DB transactions ordenadas)

Deduper usa pgxpool directamente (thread-safe nativo).

Lifecycle webhooks: fire-and-forget

Los webhooks salen en goroutines independientes con timeout 10s. Si el orquestador tarda, qrsgen no se bloquea. Esto significa que el orden de los eventos en el orquestador no está garantizado entre instancias distintas — pero sí dentro de una misma instancia.

Graceful shutdown

signal.NotifyContext(SIGTERM) activa la secuencia:

SIGTERM recibido
BroadcastBackendRestarting() → emit lifecycle a cada instancia
sleep 12s   ← el downstream drena webhooks pendientes
e.Shutdown(ctx)   ← Echo server cierra accept loop
mgr.Shutdown()   ← cierra todas las wameow.Conn (con sus WebSockets)
usage.Tracker flush final (no bloquea más de 1s)
proceso exit

Downtime efectivo del WebSocket: ~10-15 segundos (más con order: stop-first del compose, diseñado así para evitar JID conflicts). Mensajes outgoing que llegan durante esa ventana se encolan en el outbox y se entregan al volver.

Glosario

Goroutine: unidad de concurrencia en Go. Más ligera que un thread de OS — un proceso puede tener miles. qrsgen las usa para WebSockets, loops de mantenimiento y webhooks async.

Mutex (Mutual Exclusion): primitiva que protege acceso concurrente a estructuras compartidas. Solo una goroutine puede tener el lock a la vez.

RWMutex (Read-Write Mutex): variante del mutex que permite varios lectores concurrentes pero un solo escritor. Más eficiente cuando las lecturas dominan (manager.mu).

pgxpool: pool de conexiones Postgres thread-safe nativamente. Cada goroutine pide una conexión, hace su query y la devuelve.

Fire-and-forget: patrón donde se lanza una operación async y no se espera resultado. qrsgen emite lifecycle webhooks así para no bloquear si el integrador tarda.

SIGTERM: señal Unix que pide a un proceso que se cierre limpiamente (en contraste con SIGKILL, que lo mata inmediatamente). Docker envía SIGTERM al actualizar/parar containers.

signal.NotifyContext: helper Go que crea un context cancelable cuando llega una señal específica. qrsgen lo usa para orquestar el graceful shutdown.

Graceful shutdown: secuencia ordenada para cerrar el proceso sin perder datos. qrsgen emite backend_restarting, espera 12s, cierra el HTTP server, cierra los WebSockets, hace flush final del usage tracker y sale.

stop-first (update_config): política Docker Swarm donde el container viejo se detiene antes de arrancar el nuevo. Evita condiciones de carrera (dos containers compitiendo por la misma sesión WhatsApp). Tradeoff: ~15s de downtime por deploy.

JID conflict: situación donde WhatsApp ve dos clientes intentando usar la misma sesión simultáneamente, y desconecta ambos por seguridad. Lo previenes con stop-first.