|
|
|
// +build !confonly
|
|
|
|
|
|
|
|
package stats
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"v2ray.com/core/common"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Channel is an implementation of stats.Channel.
|
|
|
|
type Channel struct {
|
|
|
|
channel chan interface{}
|
|
|
|
subscribers []chan interface{}
|
|
|
|
|
|
|
|
// Synchronization components
|
|
|
|
access sync.RWMutex
|
|
|
|
closed chan struct{}
|
|
|
|
|
|
|
|
// Channel options
|
|
|
|
subscriberLimit int // Set to 0 as no subscriber limit
|
|
|
|
channelBufferSize int // Set to 0 as no buffering
|
|
|
|
broadcastTimeout time.Duration // Set to 0 as non-blocking immediate timeout
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewChannel creates an instance of Statistics Channel.
|
|
|
|
func NewChannel(config *ChannelConfig) *Channel {
|
|
|
|
return &Channel{
|
|
|
|
channel: make(chan interface{}, config.BufferSize),
|
|
|
|
subscriberLimit: int(config.SubscriberLimit),
|
|
|
|
channelBufferSize: int(config.BufferSize),
|
|
|
|
broadcastTimeout: time.Duration(config.BroadcastTimeout+1) * time.Millisecond,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Channel returns the underlying go channel.
|
|
|
|
func (c *Channel) Channel() chan interface{} {
|
|
|
|
c.access.RLock()
|
|
|
|
defer c.access.RUnlock()
|
|
|
|
return c.channel
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.subscriberLimit > 0 && len(c.subscribers) >= c.subscriberLimit {
|
|
|
|
return nil, newError("Number of subscribers has reached limit")
|
|
|
|
}
|
|
|
|
subscriber := make(chan interface{}, c.channelBufferSize)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Publish implements stats.Channel.
|
|
|
|
func (c *Channel) Publish(message interface{}) {
|
|
|
|
select { // Early exit if channel closed
|
|
|
|
case <-c.closed:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
select { // Drop message if not successfully sent
|
|
|
|
case c.channel <- message:
|
|
|
|
default:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Running returns whether the channel is running.
|
|
|
|
func (c *Channel) Running() bool {
|
|
|
|
select {
|
|
|
|
case <-c.closed: // Channel closed
|
|
|
|
default: // Channel running or not initialized
|
|
|
|
if c.closed != nil { // Channel initialized
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start implements common.Runnable.
|
|
|
|
func (c *Channel) Start() error {
|
|
|
|
c.access.Lock()
|
|
|
|
defer c.access.Unlock()
|
|
|
|
if !c.Running() {
|
|
|
|
c.closed = make(chan struct{}) // Reset close signal
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case message := <-c.channel: // Broadcast message
|
|
|
|
for _, sub := range c.Subscribers() { // Concurrency-safe subscribers retreivement
|
|
|
|
select {
|
|
|
|
case sub <- message: // Successfully sent message
|
|
|
|
case <-time.After(c.broadcastTimeout): // Remove timeout subscriber
|
|
|
|
common.Must(c.Unsubscribe(sub))
|
|
|
|
close(sub) // Actively close subscriber as notification
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case <-c.closed: // Channel closed
|
|
|
|
for _, sub := range c.Subscribers() { // Remove all subscribers
|
|
|
|
common.Must(c.Unsubscribe(sub))
|
|
|
|
close(sub)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close implements common.Closable.
|
|
|
|
func (c *Channel) Close() error {
|
|
|
|
c.access.Lock()
|
|
|
|
defer c.access.Unlock()
|
|
|
|
if c.Running() {
|
|
|
|
close(c.closed) // Send closed signal
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|