Queue
Background task queue, with a worker pool, automatic retry, and draining
on shutdown. Enabled via QUEUE_ENABLED=true.
Cache, EventBus, and Queue play different roles:
| Component | Role | Guarantee |
|---|---|---|
fw.Cache |
Key/value storage with TTL | Fast reads; data can expire |
fw.Events (EventBus) |
Fire-and-forget pub/sub | ALL subscribers receive it; no retry |
fw.Queue |
Background task queue | Each task processed by ONE worker, with retry and draining on shutdown |
Use the Queue for work that can't be lost during a spike: emails, webhooks, media processing.
Configuration
QUEUE_ENABLED=true
QUEUE_DRIVER=memory # memory | redis
QUEUE_WORKERS=4
# Only with QUEUE_DRIVER=redis — a queue shared across replicas via
# LPUSH/BRPOP, instead of a process-local channel:
QUEUE_ADDR=127.0.0.1:6379
QUEUE_PASSWORD= # Redis AUTH; empty = no password
With QUEUE_DRIVER=redis, every replica of the process needs to register
the same handlers — any replica can process a task enqueued by
another. If the connection fails at boot, bootstrap falls back to an
in-memory queue (not shared across replicas) and logs a warning, instead
of refusing to start.
Registering handlers and enqueueing
// App's Register — before any Enqueue call:
fw.Queue.Register("email.welcome", func(payload any) error {
return sendEmail(payload.(string)) // error ≠ nil → retry (3× with backoff)
})
// In the view:
if err := fw.Queue.Enqueue("email.welcome", user.Email); err != nil {
// ErrQueueFull = explicit backpressure: decide between 503, logging, or retry
log.Printf("queue full: %v", err)
}
On server shutdown the queue is drained before the process exits —
enqueued tasks aren't lost during a deploy (in redis mode, tasks still
in the list stay there for the next boot to consume). A panic in a
handler doesn't bring down the worker (it's logged with a stack trace).