XTLS/Xray-core · error

Number of subscribers has reached limit

Error message

Number of subscribers has reached limit

What it means

Thrown by stats.Manager's channel implementation when Subscribe() is called on a Channel whose subscriber count has already reached its configured subsLimit. The limit is a per-channel cap set when the channel is created (ChannelConfig); once len(subscribers) >= subsLimit (and the limit is > 0), new subscriptions are refused. This protects the process from unbounded channel/goroutine growth.

Source

Thrown at app/stats/channel.go:48

		subsLimit:  int(config.SubscriberLimit),
		bufferSize: int(config.BufferSize),
		blocking:   config.Blocking,
	}
}

// Subscribers implements stats.Channel.
func (c *Channel) Subscribers() []chan interface{} {
	c.access.RLock()
	defer c.access.RUnlock()
	return c.subscribers
}

// Subscribe implements stats.Channel.
func (c *Channel) Subscribe() (chan interface{}, error) {
	c.access.Lock()
	defer c.access.Unlock()
	if c.subsLimit > 0 && len(c.subscribers) >= c.subsLimit {
		return nil, errors.New("Number of subscribers has reached limit")
	}
	subscriber := make(chan interface{}, c.bufferSize)
	c.subscribers = append(c.subscribers, subscriber)
	return subscriber, nil
}

// Unsubscribe implements stats.Channel.
func (c *Channel) Unsubscribe(subscriber chan interface{}) error {
	c.access.Lock()
	defer c.access.Unlock()
	for i, s := range c.subscribers {
		if s == subscriber {
			// Copy to new memory block to prevent modifying original data
			subscribers := make([]chan interface{}, len(c.subscribers)-1)
			copy(subscribers[:i], c.subscribers[:i])
			copy(subscribers[i:], c.subscribers[i+1:])
			c.subscribers = subscribers
		}

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Find and fix leaked subscribers: ensure every Subscribe() result is matched by Unsubscribe(ch) when the consumer shuts down (close the handler, defer Unsubscribe).
  2. If the subscriber count is legitimately high, recreate/register the channel with a larger subsLimit via the channel's ChannelConfig.
  3. As a caller, prefer reusing one long-lived subscription per component instead of subscribing per connection/request.
  4. If you control registration, use GetOrRegisterChannel and share one channel rather than re-subscribing repeatedly.

Example fix

// before
sub, err := channel.Subscribe() // fails at N+1 when subsLimit == N
if err != nil { return err }

// after
sub, err := channel.Subscribe()
if err != nil { return err }
defer channel.Unsubscribe(sub) // keep subscriber count within subsLimit
Defensive patterns

Strategy: try-catch

Validate before calling

// Before subscribing, check remaining capacity if exposed via your wrapper:
if lim := channel.SubsLimit(); lim > 0 && channel.SubscriberCount() >= lim { /* skip or grow */ }

Try / catch

sub, err := channel.Subscribe()
if err != nil {
    if strings.Contains(err.Error(), "Number of subscribers has reached limit") {
        // recycle an existing subscription or defer retry after Unsubscribe
    }
    return err
}
defer channel.Unsubscribe(sub)

Prevention

When it happens

Trigger: Calling stats.Channel.Subscribe() on a channel constructed with a positive subsLimit after that many subscribers already exist (e.g. repeatedly creating inbound handlers/outbound observers that subscribe to the same named channel, e.g. 'outbound', 'inbound', without ever calling Unsubscribe).

Common situations: Long-running proxy instances that dynamically add/remove inbounds or API clients at runtime; feature code that subscribes per-request but leaks subscriptions; deploying third-party Xray panels/plugins that each subscribe to the same stats channel.

Related errors


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