{"record":{"id":"b22fe754e71554df","repo":"XTLS/Xray-core","slug":"number-of-subscribers-has-reached-limit","errorCode":null,"errorMessage":"Number of subscribers has reached limit","messagePattern":"Number of subscribers has reached limit","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/stats/channel.go","lineNumber":48,"sourceCode":"\t\tsubsLimit:  int(config.SubscriberLimit),\n\t\tbufferSize: int(config.BufferSize),\n\t\tblocking:   config.Blocking,\n\t}\n}\n\n// Subscribers implements stats.Channel.\nfunc (c *Channel) Subscribers() []chan interface{} {\n\tc.access.RLock()\n\tdefer c.access.RUnlock()\n\treturn c.subscribers\n}\n\n// Subscribe implements stats.Channel.\nfunc (c *Channel) Subscribe() (chan interface{}, error) {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tif c.subsLimit > 0 && len(c.subscribers) >= c.subsLimit {\n\t\treturn nil, errors.New(\"Number of subscribers has reached limit\")\n\t}\n\tsubscriber := make(chan interface{}, c.bufferSize)\n\tc.subscribers = append(c.subscribers, subscriber)\n\treturn subscriber, nil\n}\n\n// Unsubscribe implements stats.Channel.\nfunc (c *Channel) Unsubscribe(subscriber chan interface{}) error {\n\tc.access.Lock()\n\tdefer c.access.Unlock()\n\tfor i, s := range c.subscribers {\n\t\tif s == subscriber {\n\t\t\t// Copy to new memory block to prevent modifying original data\n\t\t\tsubscribers := make([]chan interface{}, len(c.subscribers)-1)\n\t\t\tcopy(subscribers[:i], c.subscribers[:i])\n\t\t\tcopy(subscribers[i:], c.subscribers[i+1:])\n\t\t\tc.subscribers = subscribers\n\t\t}","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/stats/channel.go#L30-L66","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nsub, err := channel.Subscribe() // fails at N+1 when subsLimit == N\nif err != nil { return err }\n\n// after\nsub, err := channel.Subscribe()\nif err != nil { return err }\ndefer channel.Unsubscribe(sub) // keep subscriber count within subsLimit","handlingStrategy":"try-catch","validationCode":"// Before subscribing, check remaining capacity if exposed via your wrapper:\nif lim := channel.SubsLimit(); lim > 0 && channel.SubscriberCount() >= lim { /* skip or grow */ }","typeGuard":null,"tryCatchPattern":"sub, err := channel.Subscribe()\nif err != nil {\n    if strings.Contains(err.Error(), \"Number of subscribers has reached limit\") {\n        // recycle an existing subscription or defer retry after Unsubscribe\n    }\n    return err\n}\ndefer channel.Unsubscribe(sub)","preventionTips":["Always pair Subscribe() with a deferred Unsubscribe()","Share one subscription per component instead of per request","Instrument subscriber counts to detect leaks early"],"tags":["stats","pubsub","resource-limit","xray"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}