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

  1. 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.
  2. Throttle the caller: batch inbound/client mutations instead of issuing hundreds of individual API calls within 5 seconds.
  3. Move the database to faster storage or switch to PostgreSQL (XUI_DB_TYPE=postgres) so each serialized transaction completes quicker.
  4. 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

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


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/b8412295b21ecada. Report an issue: GitHub.