{"record":{"id":"7ee30e3d87fa2765","repo":"XTLS/Xray-core","slug":"counter-s-already-registered","errorCode":null,"errorMessage":"Counter %s already registered.","messagePattern":"Counter (.+?) already registered\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/stats/stats.go","lineNumber":43,"sourceCode":"\t\tonlineMaps: make(map[string]*OnlineMap),\n\t\tchannels:   make(map[string]*Channel),\n\t}\n\n\treturn m, nil\n}\n\n// Type implements common.HasType.\nfunc (*Manager) Type() interface{} {\n\treturn stats.ManagerType()\n}\n\n// RegisterCounter implements stats.Manager.\nfunc (m *Manager) RegisterCounter(name string) (stats.Counter, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif _, found := m.counters[name]; found {\n\t\treturn nil, errors.New(\"Counter \", name, \" already registered.\")\n\t}\n\terrors.LogDebug(context.Background(), \"create new counter \", name)\n\tc := new(Counter)\n\tm.counters[name] = c\n\treturn c, nil\n}\n\n// GetOrRegisterCounter implements stats.Manager.\nfunc (m *Manager) GetOrRegisterCounter(name string) (stats.Counter, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif c, found := m.counters[name]; found {\n\t\treturn c, nil\n\t}\n\terrors.LogDebug(context.Background(), \"create new counter \", name)\n\tc := new(Counter)\n\tm.counters[name] = c","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/stats/stats.go#L25-L61","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nc, err := statsManager.RegisterCounter(\"user>>>a@b.com>>>traffic>>>uplink\")\n\n// after\nc, err := statsManager.GetOrRegisterCounter(\"user>>>a@b.com>>>traffic>>>uplink\")","handlingStrategy":"fallback","validationCode":"if c, err := m.GetOrRegisterCounter(name); err == nil { use(c) } // GetOrRegister never hits this error","typeGuard":null,"tryCatchPattern":"c, err := m.RegisterCounter(name)\nif err != nil {\n    if c2, ok := m.GetCounter(name); ok { c = c2 } else { return err } // fall back to existing\n}","preventionTips":["Prefer GetOrRegister* variants for idempotent startup code","Remove counters when the entity they track is removed","Namespace counter names by owner to avoid collisions"],"tags":["stats","duplicate-registration","api-misuse","xray"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}