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  }