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
- Find and fix leaked subscribers: ensure every Subscribe() result is matched by Unsubscribe(ch) when the consumer shuts down (close the handler, defer Unsubscribe).
- If the subscriber count is legitimately high, recreate/register the channel with a larger subsLimit via the channel's ChannelConfig.
- As a caller, prefer reusing one long-lived subscription per component instead of subscribing per connection/request.
- 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
- Always pair Subscribe() with a deferred Unsubscribe()
- Share one subscription per component instead of per request
- Instrument subscriber counts to detect leaks early
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
- Channel %s already registered.
- Counter %s already registered.
- OnlineMap %s already registered.
- metrics must have a tag or listen address
- Cannot get depended features
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/b22fe754e71554df.
Report an issue: GitHub.