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 }