gopkg.in/docker/docker.v20@v20.10.27/pkg/pubsub/publisher.go (about)

     1  package pubsub // import "github.com/docker/docker/pkg/pubsub"
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  )
     7  
     8  var wgPool = sync.Pool{New: func() interface{} { return new(sync.WaitGroup) }}
     9  
    10  // NewPublisher creates a new pub/sub publisher to broadcast messages.
    11  // The duration is used as the send timeout as to not block the publisher publishing
    12  // messages to other clients if one client is slow or unresponsive.
    13  // The buffer is used when creating new channels for subscribers.
    14  func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher {
    15  	return &Publisher{
    16  		buffer:      buffer,
    17  		timeout:     publishTimeout,
    18  		subscribers: make(map[subscriber]topicFunc),
    19  	}
    20  }
    21  
    22  type subscriber chan interface{}
    23  type topicFunc func(v interface{}) bool
    24  
    25  // Publisher is basic pub/sub structure. Allows to send events and subscribe
    26  // to them. Can be safely used from multiple goroutines.
    27  type Publisher struct {
    28  	m           sync.RWMutex
    29  	buffer      int
    30  	timeout     time.Duration
    31  	subscribers map[subscriber]topicFunc
    32  }
    33  
    34  // Len returns the number of subscribers for the publisher
    35  func (p *Publisher) Len() int {
    36  	p.m.RLock()
    37  	i := len(p.subscribers)
    38  	p.m.RUnlock()
    39  	return i
    40  }
    41  
    42  // Subscribe adds a new subscriber to the publisher returning the channel.
    43  func (p *Publisher) Subscribe() chan interface{} {
    44  	return p.SubscribeTopic(nil)
    45  }
    46  
    47  // SubscribeTopic adds a new subscriber that filters messages sent by a topic.
    48  func (p *Publisher) SubscribeTopic(topic topicFunc) chan interface{} {
    49  	ch := make(chan interface{}, p.buffer)
    50  	p.m.Lock()
    51  	p.subscribers[ch] = topic
    52  	p.m.Unlock()
    53  	return ch
    54  }
    55  
    56  // SubscribeTopicWithBuffer adds a new subscriber that filters messages sent by a topic.
    57  // The returned channel has a buffer of the specified size.
    58  func (p *Publisher) SubscribeTopicWithBuffer(topic topicFunc, buffer int) chan interface{} {
    59  	ch := make(chan interface{}, buffer)
    60  	p.m.Lock()
    61  	p.subscribers[ch] = topic
    62  	p.m.Unlock()
    63  	return ch
    64  }
    65  
    66  // Evict removes the specified subscriber from receiving any more messages.
    67  func (p *Publisher) Evict(sub chan interface{}) {
    68  	p.m.Lock()
    69  	_, exists := p.subscribers[sub]
    70  	if exists {
    71  		delete(p.subscribers, sub)
    72  		close(sub)
    73  	}
    74  	p.m.Unlock()
    75  }
    76  
    77  // Publish sends the data in v to all subscribers currently registered with the publisher.
    78  func (p *Publisher) Publish(v interface{}) {
    79  	p.m.RLock()
    80  	if len(p.subscribers) == 0 {
    81  		p.m.RUnlock()
    82  		return
    83  	}
    84  
    85  	wg := wgPool.Get().(*sync.WaitGroup)
    86  	for sub, topic := range p.subscribers {
    87  		wg.Add(1)
    88  		go p.sendTopic(sub, topic, v, wg)
    89  	}
    90  	wg.Wait()
    91  	wgPool.Put(wg)
    92  	p.m.RUnlock()
    93  }
    94  
    95  // Close closes the channels to all subscribers registered with the publisher.
    96  func (p *Publisher) Close() {
    97  	p.m.Lock()
    98  	for sub := range p.subscribers {
    99  		delete(p.subscribers, sub)
   100  		close(sub)
   101  	}
   102  	p.m.Unlock()
   103  }
   104  
   105  func (p *Publisher) sendTopic(sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup) {
   106  	defer wg.Done()
   107  	if topic != nil && !topic(v) {
   108  		return
   109  	}
   110  
   111  	// send under a select as to not block if the receiver is unavailable
   112  	if p.timeout > 0 {
   113  		timeout := time.NewTimer(p.timeout)
   114  		defer timeout.Stop()
   115  
   116  		select {
   117  		case sub <- v:
   118  		case <-timeout.C:
   119  		}
   120  		return
   121  	}
   122  
   123  	select {
   124  	case sub <- v:
   125  	default:
   126  	}
   127  }