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  }