github.com/xraypb/Xray-core@v1.8.1/app/stats/channel.go (about) 1 package stats 2 3 import ( 4 "context" 5 "sync" 6 7 "github.com/xraypb/Xray-core/common" 8 ) 9 10 // Channel is an implementation of stats.Channel. 11 type Channel struct { 12 channel chan channelMessage 13 subscribers []chan interface{} 14 15 // Synchronization components 16 access sync.RWMutex 17 closed chan struct{} 18 19 // Channel options 20 blocking bool // Set blocking state if channel buffer reaches limit 21 bufferSize int // Set to 0 as no buffering 22 subsLimit int // Set to 0 as no subscriber limit 23 } 24 25 // NewChannel creates an instance of Statistics Channel. 26 func NewChannel(config *ChannelConfig) *Channel { 27 return &Channel{ 28 channel: make(chan channelMessage, config.BufferSize), 29 subsLimit: int(config.SubscriberLimit), 30 bufferSize: int(config.BufferSize), 31 blocking: config.Blocking, 32 } 33 } 34 35 // Subscribers implements stats.Channel. 36 func (c *Channel) Subscribers() []chan interface{} { 37 c.access.RLock() 38 defer c.access.RUnlock() 39 return c.subscribers 40 } 41 42 // Subscribe implements stats.Channel. 43 func (c *Channel) Subscribe() (chan interface{}, error) { 44 c.access.Lock() 45 defer c.access.Unlock() 46 if c.subsLimit > 0 && len(c.subscribers) >= c.subsLimit { 47 return nil, newError("Number of subscribers has reached limit") 48 } 49 subscriber := make(chan interface{}, c.bufferSize) 50 c.subscribers = append(c.subscribers, subscriber) 51 return subscriber, nil 52 } 53 54 // Unsubscribe implements stats.Channel. 55 func (c *Channel) Unsubscribe(subscriber chan interface{}) error { 56 c.access.Lock() 57 defer c.access.Unlock() 58 for i, s := range c.subscribers { 59 if s == subscriber { 60 // Copy to new memory block to prevent modifying original data 61 subscribers := make([]chan interface{}, len(c.subscribers)-1) 62 copy(subscribers[:i], c.subscribers[:i]) 63 copy(subscribers[i:], c.subscribers[i+1:]) 64 c.subscribers = subscribers 65 } 66 } 67 return nil 68 } 69 70 // Publish implements stats.Channel. 71 func (c *Channel) Publish(ctx context.Context, msg interface{}) { 72 select { // Early exit if channel closed 73 case <-c.closed: 74 return 75 default: 76 pub := channelMessage{context: ctx, message: msg} 77 if c.blocking { 78 pub.publish(c.channel) 79 } else { 80 pub.publishNonBlocking(c.channel) 81 } 82 } 83 } 84 85 // Running returns whether the channel is running. 86 func (c *Channel) Running() bool { 87 select { 88 case <-c.closed: // Channel closed 89 default: // Channel running or not initialized 90 if c.closed != nil { // Channel initialized 91 return true 92 } 93 } 94 return false 95 } 96 97 // Start implements common.Runnable. 98 func (c *Channel) Start() error { 99 c.access.Lock() 100 defer c.access.Unlock() 101 if !c.Running() { 102 c.closed = make(chan struct{}) // Reset close signal 103 go func() { 104 for { 105 select { 106 case pub := <-c.channel: // Published message received 107 for _, sub := range c.Subscribers() { // Concurrency-safe subscribers retrievement 108 if c.blocking { 109 pub.broadcast(sub) 110 } else { 111 pub.broadcastNonBlocking(sub) 112 } 113 } 114 case <-c.closed: // Channel closed 115 for _, sub := range c.Subscribers() { // Remove all subscribers 116 common.Must(c.Unsubscribe(sub)) 117 close(sub) 118 } 119 return 120 } 121 } 122 }() 123 } 124 return nil 125 } 126 127 // Close implements common.Closable. 128 func (c *Channel) Close() error { 129 c.access.Lock() 130 defer c.access.Unlock() 131 if c.Running() { 132 close(c.closed) // Send closed signal 133 } 134 return nil 135 } 136 137 // channelMessage is the published message with guaranteed delivery. 138 // message is discarded only when the context is early cancelled. 139 type channelMessage struct { 140 context context.Context 141 message interface{} 142 } 143 144 func (c channelMessage) publish(publisher chan channelMessage) { 145 select { 146 case publisher <- c: 147 case <-c.context.Done(): 148 } 149 } 150 151 func (c channelMessage) publishNonBlocking(publisher chan channelMessage) { 152 select { 153 case publisher <- c: 154 default: // Create another goroutine to keep sending message 155 go c.publish(publisher) 156 } 157 } 158 159 func (c channelMessage) broadcast(subscriber chan interface{}) { 160 select { 161 case subscriber <- c.message: 162 case <-c.context.Done(): 163 } 164 } 165 166 func (c channelMessage) broadcastNonBlocking(subscriber chan interface{}) { 167 select { 168 case subscriber <- c.message: 169 default: // Create another goroutine to keep sending message 170 go c.broadcast(subscriber) 171 } 172 }