github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/watch.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/names/v5" 12 "gopkg.in/tomb.v2" 13 14 apiservererrors "github.com/juju/juju/apiserver/errors" 15 "github.com/juju/juju/apiserver/facade" 16 "github.com/juju/juju/rpc/params" 17 "github.com/juju/juju/state" 18 "github.com/juju/juju/state/watcher" 19 ) 20 21 // AgentEntityWatcher implements a common Watch method for use by 22 // various facades. 23 type AgentEntityWatcher struct { 24 st state.EntityFinder 25 resources facade.Resources 26 getCanWatch GetAuthFunc 27 } 28 29 // NewAgentEntityWatcher returns a new AgentEntityWatcher. The 30 // GetAuthFunc will be used on each invocation of Watch to determine 31 // current permissions. 32 func NewAgentEntityWatcher(st state.EntityFinder, resources facade.Resources, getCanWatch GetAuthFunc) *AgentEntityWatcher { 33 return &AgentEntityWatcher{ 34 st: st, 35 resources: resources, 36 getCanWatch: getCanWatch, 37 } 38 } 39 40 func (a *AgentEntityWatcher) watchEntity(tag names.Tag) (string, error) { 41 entity0, err := a.st.FindEntity(tag) 42 if err != nil { 43 return "", err 44 } 45 entity, ok := entity0.(state.NotifyWatcherFactory) 46 if !ok { 47 return "", apiservererrors.NotSupportedError(tag, "watching") 48 } 49 watch := entity.Watch() 50 // Consume the initial event. Technically, API 51 // calls to Watch 'transmit' the initial event 52 // in the Watch response. But NotifyWatchers 53 // have no state to transmit. 54 if _, ok := <-watch.Changes(); ok { 55 return a.resources.Register(watch), nil 56 } 57 return "", watcher.EnsureErr(watch) 58 } 59 60 // Watch starts an NotifyWatcher for each given entity. 61 func (a *AgentEntityWatcher) Watch(args params.Entities) (params.NotifyWatchResults, error) { 62 result := params.NotifyWatchResults{ 63 Results: make([]params.NotifyWatchResult, len(args.Entities)), 64 } 65 if len(args.Entities) == 0 { 66 return result, nil 67 } 68 canWatch, err := a.getCanWatch() 69 if err != nil { 70 return params.NotifyWatchResults{}, errors.Trace(err) 71 } 72 for i, entity := range args.Entities { 73 tag, err := names.ParseTag(entity.Tag) 74 if err != nil { 75 result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm) 76 continue 77 } 78 err = apiservererrors.ErrPerm 79 watcherId := "" 80 if canWatch(tag) { 81 watcherId, err = a.watchEntity(tag) 82 } 83 result.Results[i].NotifyWatcherId = watcherId 84 result.Results[i].Error = apiservererrors.ServerError(err) 85 } 86 return result, nil 87 } 88 89 // MultiNotifyWatcher implements state.NotifyWatcher, combining 90 // multiple NotifyWatchers. 91 type MultiNotifyWatcher struct { 92 tomb tomb.Tomb 93 watchers []state.NotifyWatcher 94 changes chan struct{} 95 } 96 97 // NewMultiNotifyWatcher creates a NotifyWatcher that combines 98 // each of the NotifyWatchers passed in. Each watcher's initial 99 // event is consumed, and a single initial event is sent. 100 // Subsequent events are not coalesced. 101 func NewMultiNotifyWatcher(w ...state.NotifyWatcher) *MultiNotifyWatcher { 102 m := &MultiNotifyWatcher{ 103 watchers: w, 104 changes: make(chan struct{}), 105 } 106 var wg sync.WaitGroup 107 wg.Add(len(w)) 108 staging := make(chan struct{}) 109 for _, w := range w { 110 // Consume the first event of each watcher. 111 <-w.Changes() 112 go func(wCopy state.NotifyWatcher) { 113 defer wg.Done() 114 _ = wCopy.Wait() 115 }(w) 116 // Copy events from the watcher to the staging channel. 117 go copyEvents(staging, w.Changes(), &m.tomb) 118 } 119 m.tomb.Go(func() error { 120 m.loop(staging) 121 wg.Wait() 122 return nil 123 }) 124 return m 125 } 126 127 // loop copies events from the input channel to the output channel, 128 // coalescing events by waiting a short time between receiving and 129 // sending. 130 func (w *MultiNotifyWatcher) loop(in <-chan struct{}) { 131 defer close(w.changes) 132 // out is initialised to m.changes to send the initial event. 133 out := w.changes 134 var timer <-chan time.Time 135 for { 136 select { 137 case <-w.tomb.Dying(): 138 return 139 case <-in: 140 if timer == nil { 141 // TODO(fwereade): 2016-03-17 lp:1558657 142 timer = time.After(10 * time.Millisecond) 143 } 144 case <-timer: 145 timer = nil 146 out = w.changes 147 case out <- struct{}{}: 148 out = nil 149 } 150 } 151 } 152 153 // copyEvents copies channel events from "in" to "out", coalescing. 154 func copyEvents(out chan<- struct{}, in <-chan struct{}, tomb *tomb.Tomb) { 155 var outC chan<- struct{} 156 for { 157 select { 158 case <-tomb.Dying(): 159 return 160 case _, ok := <-in: 161 if !ok { 162 return 163 } 164 outC = out 165 case outC <- struct{}{}: 166 outC = nil 167 } 168 } 169 } 170 171 func (w *MultiNotifyWatcher) Kill() { 172 w.tomb.Kill(nil) 173 for _, w := range w.watchers { 174 w.Kill() 175 } 176 } 177 178 func (w *MultiNotifyWatcher) Wait() error { 179 return w.tomb.Wait() 180 } 181 182 func (w *MultiNotifyWatcher) Stop() error { 183 w.Kill() 184 return w.Wait() 185 } 186 187 func (w *MultiNotifyWatcher) Err() error { 188 return w.tomb.Err() 189 } 190 191 func (w *MultiNotifyWatcher) Changes() <-chan struct{} { 192 return w.changes 193 }