github.com/xmplusdev/xray-core@v1.8.10/app/stats/channel.go (about)

     1  package stats
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  
     7  	"github.com/xmplusdev/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  }