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