MHSanaei/3x-ui · error
traffic writer queue full
Error message
traffic writer queue full
What it means
The traffic writer is a single goroutine that serializes all DB writes through a bounded channel (capacity 256, see trafficWriterQueueSize in internal/web/service/traffic_writer.go:17). submitTrafficWrite holds twMu, waits up to trafficWriterSubmitTimeout (5s) for a free queue slot, and returns this error when the timer fires first. It means the writer goroutine is draining slower than callers are producing write requests — classic backpressure, typically caused by slow disk/SQLite lock contention or a burst of admin mutations colliding with the every-5s traffic poll.
Source
Thrown at internal/web/service/traffic_writer.go:168
twMu.Unlock()
return safeApply(fn)
}
select {
case <-ctx.Done():
twMu.Unlock()
return safeApply(fn)
default:
}
timer := time.NewTimer(trafficWriterSubmitTimeout)
defer timer.Stop()
select {
case queue <- req:
twMu.Unlock()
case <-timer.C:
twMu.Unlock()
return errors.New("traffic writer queue full")
}
select {
case err := <-req.done:
return err
case <-done:
select {
case err := <-req.done:
return err
default:
return errors.New("traffic writer stopped before write completed")
}
}
}
View on GitHub (pinned to ad32144c42)
Solutions
- Check DB health: for SQLite look for 'database is locked' in logs; for Postgres look for SQLSTATE 40P60/40P01 deadlocks or long transactions (pg_stat_activity) that are stalling the single writer goroutine.
- Throttle the caller: batch inbound/client mutations instead of issuing hundreds of individual API calls within 5 seconds.
- Move the database to faster storage or switch to PostgreSQL (XUI_DB_TYPE=postgres) so each serialized transaction completes quicker.
- If the condition is chronic, raise trafficWriterQueueSize and/or trafficWriterSubmitTimeout in internal/web/service/traffic_writer.go:17-18 and rebuild — but only after fixing the underlying slow-write cause.
Example fix
// before (caller loop filling the queue)
for _, c := range clients {
inboundService.UpdateClientTraffic(c) // may hit "traffic writer queue full"
}
// after: batch into one serialized transaction
err := inboundService.UpdateClientsTraffic(clients) // one submitTrafficWrite Defensive patterns
Strategy: retry
Validate before calling
// Not directly callable from outside the package; callers of inbound/client
// service methods can shed load before bursts:
if len(pendingMutations) > 200 { // queue is 256 deep
waitOrBatch()
} Try / catch
err := svc.UpdateClient(...)
if err != nil && strings.Contains(err.Error(), "traffic writer queue full") {
time.Sleep(trafficWriterSubmitTimeout) // let the writer drain
err = svc.UpdateClient(...) // one bounded retry
}
return err Prevention
- Batch bulk client/inbound edits into a single API call instead of hundreds of individual requests.
- Keep DB latency low: fast disk for SQLite, or Postgres with no long-running transactions contending on client_traffics/inbounds rows.
- Monitor for this message in logs during automation runs; it is the earliest signal the serialized writer is saturating.
When it happens
Trigger: More than 256 pending runSerializedTx submissions (client/inbound edits, traffic resets) while the consumer goroutine is blocked on a slow DB transaction; e.g. a bulk client import or scripted API loop issuing many inbound updates at once, or SQLite stuck on a busy_timeout against a long reader.
Common situations: SQLite on a slow/network volume, Postgres deadlock retries serializing behind one transaction, automation scripts hammering /panel/api inbound update endpoints, or a hung DB connection making the single writer goroutine stall while the queue fills.
Related errors
- sqlite backup is unavailable for PostgreSQL
- source sqlite not found at %s: %w
- copy %T: %w
- XUI_DB_TYPE=postgres but XUI_DB_DSN is empty
- database is not initialized
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/b8412295b21ecada.
Report an issue: GitHub.