github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/watcher/multinotify.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package watcher
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"gopkg.in/tomb.v2"
    11  )
    12  
    13  // MultiNotifyWatcher implements NotifyWatcher, combining
    14  // multiple NotifyWatchers.
    15  type MultiNotifyWatcher struct {
    16  	tomb     tomb.Tomb
    17  	watchers []NotifyWatcher
    18  	changes  chan struct{}
    19  }
    20  
    21  // NewMultiNotifyWatcher creates a NotifyWatcher that combines
    22  // each of the NotifyWatchers passed in. Each watcher's initial
    23  // event is consumed, and a single initial event is sent.
    24  // Subsequent events are not coalesced.
    25  func NewMultiNotifyWatcher(w ...NotifyWatcher) *MultiNotifyWatcher {
    26  	m := &MultiNotifyWatcher{
    27  		watchers: w,
    28  		changes:  make(chan struct{}),
    29  	}
    30  	var wg sync.WaitGroup
    31  	wg.Add(len(w))
    32  	staging := make(chan struct{})
    33  	for _, w := range w {
    34  		// Consume the first event of each watcher.
    35  		<-w.Changes()
    36  		go func(wCopy NotifyWatcher) {
    37  			defer wg.Done()
    38  			_ = wCopy.Wait()
    39  		}(w)
    40  		// Copy events from the watcher to the staging channel.
    41  		go copyEvents(staging, w.Changes(), &m.tomb)
    42  	}
    43  	m.tomb.Go(func() error {
    44  		m.loop(staging)
    45  		wg.Wait()
    46  		return nil
    47  	})
    48  	return m
    49  }
    50  
    51  // loop copies events from the input channel to the output channel,
    52  // coalescing events by waiting a short time between receiving and
    53  // sending.
    54  func (w *MultiNotifyWatcher) loop(in <-chan struct{}) {
    55  	defer close(w.changes)
    56  	// out is initialised to m.changes to send the initial event.
    57  	out := w.changes
    58  	var timer <-chan time.Time
    59  	for {
    60  		select {
    61  		case <-w.tomb.Dying():
    62  			return
    63  		case <-in:
    64  			if timer == nil {
    65  				// TODO(fwereade): 2016-03-17 lp:1558657
    66  				timer = time.After(10 * time.Millisecond)
    67  			}
    68  		case <-timer:
    69  			timer = nil
    70  			out = w.changes
    71  		case out <- struct{}{}:
    72  			out = nil
    73  		}
    74  	}
    75  }
    76  
    77  // copyEvents copies channel events from "in" to "out", coalescing.
    78  func copyEvents(out chan<- struct{}, in <-chan struct{}, tomb *tomb.Tomb) {
    79  	var outC chan<- struct{}
    80  	for {
    81  		select {
    82  		case <-tomb.Dying():
    83  			return
    84  		case _, ok := <-in:
    85  			if !ok {
    86  				return
    87  			}
    88  			outC = out
    89  		case outC <- struct{}{}:
    90  			outC = nil
    91  		}
    92  	}
    93  }
    94  
    95  func (w *MultiNotifyWatcher) Kill() {
    96  	w.tomb.Kill(nil)
    97  	for _, w := range w.watchers {
    98  		w.Kill()
    99  	}
   100  }
   101  
   102  func (w *MultiNotifyWatcher) Wait() error {
   103  	return w.tomb.Wait()
   104  }
   105  
   106  func (w *MultiNotifyWatcher) Stop() error {
   107  	w.Kill()
   108  	return w.Wait()
   109  }
   110  
   111  func (w *MultiNotifyWatcher) Err() error {
   112  	return w.tomb.Err()
   113  }
   114  
   115  func (w *MultiNotifyWatcher) Changes() NotifyChannel {
   116  	return w.changes
   117  }