github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/utils/stringforwarder/stringforwarder.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package stringforwarder
     5  
     6  import "sync"
     7  
     8  // StringForwarder is a goroutine-safe type that pipes messages from the
     9  // its Forward() method, sending them to callback.  The send will not be
    10  // blocked by the callback, but will instead discard messages if there
    11  // is an incomplete callback in progress. The number of discarded messages
    12  // is tracked and returned when the forwarder is stopped.
    13  type StringForwarder struct {
    14  	mu           sync.Mutex
    15  	cond         *sync.Cond
    16  	current      *string
    17  	stopped      bool
    18  	discardCount uint64
    19  }
    20  
    21  // New returns a new StringForwarder that sends messages to the callback,
    22  // function, dropping messages if the receiver has not yet consumed the
    23  // last message.
    24  func New(callback func(string)) *StringForwarder {
    25  	if callback == nil {
    26  		// Nothing to forward to, so no need to start the loop().
    27  		// We'll just count the discardCount.
    28  		return &StringForwarder{stopped: true}
    29  	}
    30  	forwarder := &StringForwarder{}
    31  	forwarder.cond = sync.NewCond(&forwarder.mu)
    32  	go forwarder.loop(callback)
    33  	return forwarder
    34  }
    35  
    36  // Forward sends the message to be processed by the callback function,
    37  // discarding the message if another message is currently being processed.
    38  // The number of discarded messages is recorded for reporting by the Stop
    39  // method.
    40  //
    41  // Forward is safe to call from multiple goroutines at once.
    42  // Note that if this StringForwarder was created with a nil callback, all
    43  // messages will be discarded.
    44  func (f *StringForwarder) Forward(msg string) {
    45  	f.mu.Lock()
    46  	if f.stopped || f.current != nil {
    47  		f.discardCount++
    48  	} else {
    49  		f.current = &msg
    50  		f.cond.Signal()
    51  	}
    52  	f.mu.Unlock()
    53  }
    54  
    55  // Stop cleans up the goroutine running behind StringForwarder and returns the
    56  // count of discarded messages. Stop is thread-safe and may be called multiple
    57  // times - after the first time, it simply returns the current discard count.
    58  func (f *StringForwarder) Stop() uint64 {
    59  	var count uint64
    60  	f.mu.Lock()
    61  	if !f.stopped {
    62  		f.stopped = true
    63  		f.cond.Signal()
    64  	}
    65  	count = f.discardCount
    66  	f.mu.Unlock()
    67  	return count
    68  }
    69  
    70  // loop invokes forwarded messages with the given callback until stopped.
    71  func (f *StringForwarder) loop(callback func(string)) {
    72  	f.mu.Lock()
    73  	defer f.mu.Unlock()
    74  	for {
    75  		for !f.stopped && f.current == nil {
    76  			f.cond.Wait()
    77  		}
    78  		if f.current == nil {
    79  			return
    80  		}
    81  		f.invokeCallback(callback, *f.current)
    82  		f.current = nil
    83  	}
    84  }
    85  
    86  // invokeCallback invokes the given callback with a message,
    87  // unlocking the forwarder's mutex for the duration of the
    88  // callback.
    89  func (f *StringForwarder) invokeCallback(callback func(string), msg string) {
    90  	f.mu.Unlock()
    91  	defer f.mu.Lock()
    92  	callback(msg)
    93  }