XTLS/Xray-core · error
Counter %s already registered.
Error message
Counter %s already registered.
What it means
Returned by stats.Manager.RegisterCounter when a counter with the same name already exists in the manager's registry. Registration is exclusive by design so that metric identity (e.g. 'user>>>email>>>traffic>>>uplink') is unambiguous; the manager offers GetOrRegisterCounter for the idempotent path. The name is interpolated into the message by xray's variadic errors.New.
Source
Thrown at app/stats/stats.go:43
onlineMaps: make(map[string]*OnlineMap),
channels: make(map[string]*Channel),
}
return m, nil
}
// Type implements common.HasType.
func (*Manager) Type() interface{} {
return stats.ManagerType()
}
// RegisterCounter implements stats.Manager.
func (m *Manager) RegisterCounter(name string) (stats.Counter, error) {
m.access.Lock()
defer m.access.Unlock()
if _, found := m.counters[name]; found {
return nil, errors.New("Counter ", name, " already registered.")
}
errors.LogDebug(context.Background(), "create new counter ", name)
c := new(Counter)
m.counters[name] = c
return c, nil
}
// GetOrRegisterCounter implements stats.Manager.
func (m *Manager) GetOrRegisterCounter(name string) (stats.Counter, error) {
m.access.Lock()
defer m.access.Unlock()
if c, found := m.counters[name]; found {
return c, nil
}
errors.LogDebug(context.Background(), "create new counter ", name)
c := new(Counter)
m.counters[name] = cView on GitHub (pinned to 7d214f8b09)
Solutions
- Switch to GetOrRegisterCounter(name), which returns the existing counter instead of erroring.
- If the duplicate is unexpected, log the existing registration (VisitCounters) to find which component registered the name first and remove the duplicate call.
- When removing a user/handler, call RemoveCounter so a later re-add can register cleanly.
Example fix
// before
c, err := statsManager.RegisterCounter("user>>>a@b.com>>>traffic>>>uplink")
// after
c, err := statsManager.GetOrRegisterCounter("user>>>a@b.com>>>traffic>>>uplink") Defensive patterns
Strategy: fallback
Validate before calling
if c, err := m.GetOrRegisterCounter(name); err == nil { use(c) } // GetOrRegister never hits this error Try / catch
c, err := m.RegisterCounter(name)
if err != nil {
if c2, ok := m.GetCounter(name); ok { c = c2 } else { return err } // fall back to existing
} Prevention
- Prefer GetOrRegister* variants for idempotent startup code
- Remove counters when the entity they track is removed
- Namespace counter names by owner to avoid collisions
When it happens
Trigger: Calling RegisterCounter(name) twice with the same name on one Manager instance — typically when a handler is added/removed and re-added (dynamic inbound/outbound users) and startup code re-registers the same counter name.
Common situations: Panels or automation that add users twice; reloading handlers without tearing down previous stats; racing initialization code that registers 'inbound>>>tag>>>traffic>>>downlink' both at server start and when the API adds a user.
Related errors
- OnlineMap %s already registered.
- Channel %s already registered.
- Number of subscribers has reached limit
- no valid FakeDNS config
- unknown type
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/7ee30e3d87fa2765.
Report an issue: GitHub.