XTLS/Xray-core · error
no mux client worker available
Error message
no mux client worker available
What it means
StaticMuxPicker.PickAvailable found workers in the pool but none usable: every registered PortalWorker was skipped because w.draining is true (worker closing, removed after inactivity/24h timer) or w.IsFull() is true (mux worker hit its connection/cool-down capacity, hysteresis full state). Unlike error 93, the bridge tunnels exist — they are just saturated or draining.
Source
Thrown at app/reverse/portal.go:214
}
if minIdx == -1 {
for i, w := range p.workers {
if w.IsFull() {
continue
}
if w.client.ActiveConnections() < minConn {
minConn = w.client.ActiveConnections()
minIdx = i
}
}
}
if minIdx != -1 {
return p.workers[minIdx].client, nil
}
return nil, errors.New("no mux client worker available")
}
func (p *StaticMuxPicker) AddWorker(worker *PortalWorker) {
p.access.Lock()
defer p.access.Unlock()
p.workers = append(p.workers, worker)
}
type PortalWorker struct {
client *mux.ClientWorker
control *task.Periodic
writer buf.Writer
reader buf.Reader
draining bool
counter uint32
timer *signal.ActivityTimer
}View on GitHub (pinned to 7d214f8b09)
Solutions
- Add more bridges in the reverse config so the picker can spread connections and always find a non-full worker.
- Reduce multiplexing pressure: lower concurrent tunneled connections, or route bulk traffic outside the tunnel.
- If it appears right after restarts, wait for the bridge to re-register a fresh worker (monitor interval ~2s) and confirm errors 91-95 are not firing.
Example fix
// before
"reverse": { "portals": [ { "tag": "portal", "domain": "svc.reverse.internal" } ],
"bridges": [ { "tag": "bridge-1", "domain": "svc.reverse.internal" } ] }
// after: add capacity
"reverse": { "portals": [ { "tag": "portal", "domain": "svc.reverse.internal" } ],
"bridges": [ { "tag": "bridge-1", "domain": "svc.reverse.internal" },
{ "tag": "bridge-2", "domain": "svc2.reverse.internal" } ] } Defensive patterns
Strategy: fallback
Validate before calling
// Capacity pre-check: at least one non-draining, non-full worker
if !picker.HasAvailable() { // iterate workers: !draining && !IsFull()
return routeViaAlternatePath(ctx, link)
} Type guard
func pickerHasCapacity(picker reverse.WorkerCounter) bool {
return picker.HasAvailable() // false when all workers draining or IsFull()
} Try / catch
if err := dispatch(ctx, link); err != nil {
if strings.Contains(err.Error(), "no mux client worker available") {
return retryWithBackoff(ctx, link, attempts=3) // workers free up / re-register quickly
}
return err
} Prevention
- Scale bridge count with offered tunneled-connection load
- Watch worker saturation metrics; add bridges before peak hours
- During reconnect storms, drain old workers only after new ones register (capacity-aware ops)
When it happens
Trigger: All workers simultaneously in draining or IsFull state at pick time: a burst of tunneled connections filling each mux worker's connection limit, or a mass reconnect draining old workers while new ones have not been added yet.
Common situations: Heavy load through a single bridge connection (all tunneled streams multiplexed over it until capacity), bridge reconnect storms marking old workers draining, or too few bridges for the offered load. Users see intermittent 'no mux client worker available' during peaks.
Related errors
- failed to create mux client worker
- failed to create portal worker
- empty worker list
- unable to dispatch control connection
- failed to process mux outbound traffic
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/0f7229d223ad90d8.
Report an issue: GitHub.