{"record":{"id":"ce9a0e7c24184f26","repo":"XTLS/Xray-core","slug":"channel-s-already-registered","errorCode":null,"errorMessage":"Channel %s already registered.","messagePattern":"Channel (.+?) already registered\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/stats/stats.go","lineNumber":169,"sourceCode":"// VisitOnlineMaps calls visitor function on all managed online maps.\n// The visitor runs under a read lock; it must not call RegisterOnlineMap or UnregisterOnlineMap (would deadlock).\nfunc (m *Manager) VisitOnlineMaps(visitor func(string, stats.OnlineMap) bool) {\n\tm.access.RLock()\n\tdefer m.access.RUnlock()\n\tfor name, om := range m.onlineMaps {\n\t\tif !visitor(name, om) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// RegisterChannel implements stats.Manager.\nfunc (m *Manager) RegisterChannel(name string) (stats.Channel, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif _, found := m.channels[name]; found {\n\t\treturn nil, errors.New(\"Channel \", name, \" already registered.\")\n\t}\n\terrors.LogDebug(context.Background(), \"create new channel \", name)\n\tc := NewChannel(&ChannelConfig{BufferSize: 64, Blocking: false})\n\tm.channels[name] = c\n\tif m.running {\n\t\treturn c, c.Start()\n\t}\n\treturn c, nil\n}\n\n// GetOrRegisterChannel implements stats.Manager.\nfunc (m *Manager) GetOrRegisterChannel(name string) (stats.Channel, error) {\n\tm.access.Lock()\n\tdefer m.access.Unlock()\n\n\tif c, found := m.channels[name]; found {\n\t\treturn c, nil\n\t}","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/stats/stats.go#L151-L187","documentation":"Returned by stats.Manager.RegisterChannel when a stats channel with the same name already exists. Channels are pub/sub buses (used e.g. for log or event streaming) keyed by name; note that if the manager is running, RegisterChannel also starts the channel, so duplicates would double-start the bus — hence the exclusive check. Idempotent alternative: GetOrRegisterChannel.","triggerScenarios":"Calling RegisterChannel(name) twice — e.g. two components (an API logger and a panel plugin) both registering a channel named 'logger', or re-initializing a feature after config reload on a shared Manager.","commonSituations":"Integrating multiple third-party extensions that assume they own a well-known channel name; reloading configurations in-process without creating a fresh stats Manager.","solutions":["Use GetOrRegisterChannel(name) to share the existing channel instance.","Verify teardown: Unregister/remove the channel before re-registering after a config reload.","Namespace channel names per component if simultaneous independent channels are intended."],"exampleFix":"// before\nch, err := statsManager.RegisterChannel(\"events\")\n\n// after\nch, err := statsManager.GetOrRegisterChannel(\"events\")","handlingStrategy":"fallback","validationCode":"if _, found := m.GetChannel(name); found { /* reuse existing channel */ }","typeGuard":null,"tryCatchPattern":"ch, err := m.RegisterChannel(name)\nif err != nil {\n    if existing, ok2 := m.GetChannel(name); ok2 { ch = existing } else { return err }\n}","preventionTips":["Use GetOrRegisterChannel for well-known shared channels","Namespace channel names per integrating component"],"tags":["stats","pubsub","duplicate-registration","xray"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}