XTLS/Xray-core · error

Channel %s already registered.

Error message

Channel %s already registered.

What it means

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.

Source

Thrown at app/stats/stats.go:169

// VisitOnlineMaps calls visitor function on all managed online maps.
// The visitor runs under a read lock; it must not call RegisterOnlineMap or UnregisterOnlineMap (would deadlock).
func (m *Manager) VisitOnlineMaps(visitor func(string, stats.OnlineMap) bool) {
	m.access.RLock()
	defer m.access.RUnlock()
	for name, om := range m.onlineMaps {
		if !visitor(name, om) {
			break
		}
	}
}

// RegisterChannel implements stats.Manager.
func (m *Manager) RegisterChannel(name string) (stats.Channel, error) {
	m.access.Lock()
	defer m.access.Unlock()

	if _, found := m.channels[name]; found {
		return nil, errors.New("Channel ", name, " already registered.")
	}
	errors.LogDebug(context.Background(), "create new channel ", name)
	c := NewChannel(&ChannelConfig{BufferSize: 64, Blocking: false})
	m.channels[name] = c
	if m.running {
		return c, c.Start()
	}
	return c, nil
}

// GetOrRegisterChannel implements stats.Manager.
func (m *Manager) GetOrRegisterChannel(name string) (stats.Channel, error) {
	m.access.Lock()
	defer m.access.Unlock()

	if c, found := m.channels[name]; found {
		return c, nil
	}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Use GetOrRegisterChannel(name) to share the existing channel instance.
  2. Verify teardown: Unregister/remove the channel before re-registering after a config reload.
  3. Namespace channel names per component if simultaneous independent channels are intended.

Example fix

// before
ch, err := statsManager.RegisterChannel("events")

// after
ch, err := statsManager.GetOrRegisterChannel("events")
Defensive patterns

Strategy: fallback

Validate before calling

if _, found := m.GetChannel(name); found { /* reuse existing channel */ }

Try / catch

ch, err := m.RegisterChannel(name)
if err != nil {
    if existing, ok2 := m.GetChannel(name); ok2 { ch = existing } else { return err }
}

Prevention

When it happens

Trigger: 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.

Common situations: Integrating multiple third-party extensions that assume they own a well-known channel name; reloading configurations in-process without creating a fresh stats Manager.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/ce9a0e7c24184f26. Report an issue: GitHub.