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