github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/multiwatcher/watcher.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package multiwatcher 5 6 import ( 7 "github.com/juju/errors" 8 9 "github.com/juju/juju/core/multiwatcher" 10 ) 11 12 type control interface { 13 Dying() <-chan struct{} 14 Err() error 15 } 16 17 // Watcher watches any changes to the state. 18 type Watcher struct { 19 request chan *request 20 control control 21 logger Logger 22 err chan error 23 24 filter func([]multiwatcher.Delta) []multiwatcher.Delta 25 26 // The following fields are maintained by the Worker goroutine. 27 revno int64 28 stopped bool 29 30 // used indicates that the watcher was used (i.e. Next() called). 31 used bool 32 } 33 34 // Stop stops the watcher. 35 func (w *Watcher) Stop() error { 36 select { 37 case w.request <- &request{watcher: w}: 38 // We asked to be stopped, and this is processed. 39 case _, stillOpen := <-w.err: 40 // We have been stopped already. 41 if stillOpen { 42 close(w.err) 43 } 44 // Since we are asking to stop, we don't care about the underlying 45 // error from the read. 46 case <-w.control.Dying(): 47 // The worker is dying and we will be cleaned up. 48 } 49 // In all of the above cases, we're fine, and don't need to return an 50 // error that would be logged. 51 return nil 52 } 53 54 // Next retrieves all changes that have happened since the last 55 // time it was called, blocking until there are some changes available. 56 // 57 // The result from the initial call to Next() is different from 58 // subsequent calls. The latter will reflect changes that have happened 59 // since the last Next() call. In contrast, the initial Next() call will 60 // return the deltas that represent the model's complete state at that 61 // moment, even when the model is empty. In that empty model case an 62 // empty set of deltas is returned. 63 func (w *Watcher) Next() ([]multiwatcher.Delta, error) { 64 // In order to be able to apply the filter, yet not signal the caller when 65 // all deltas were filtered out, we need an outer loop. 66 var changes []multiwatcher.Delta 67 for len(changes) == 0 { 68 req := &request{ 69 watcher: w, 70 reply: make(chan bool), 71 } 72 if !w.used { 73 req.noChanges = make(chan struct{}) 74 w.used = true 75 } 76 77 select { 78 case err, open := <-w.err: 79 if open { 80 w.logger.Tracef("hit an error: %v", err) 81 close(w.err) 82 return nil, errors.Trace(err) 83 } 84 w.logger.Tracef("err channel closed") 85 return nil, errors.Trace(multiwatcher.NewErrStopped()) 86 case <-w.control.Dying(): 87 w.logger.Tracef("worker dying") 88 err := w.control.Err() 89 if err == nil { 90 err = multiwatcher.ErrStoppedf("shared state watcher") 91 } 92 return nil, err 93 case w.request <- req: 94 } 95 96 select { 97 case err, open := <-w.err: 98 if open { 99 w.logger.Tracef("hit an error: %v", err) 100 close(w.err) 101 return nil, errors.Trace(err) 102 } 103 w.logger.Tracef("err channel closed") 104 return nil, errors.Trace(multiwatcher.NewErrStopped()) 105 case <-w.control.Dying(): 106 w.logger.Tracef("worker dying") 107 err := w.control.Err() 108 if err == nil { 109 err = multiwatcher.ErrStoppedf("shared state watcher") 110 } 111 return nil, err 112 case ok := <-req.reply: 113 if !ok { 114 return nil, errors.Trace(multiwatcher.NewErrStopped()) 115 } 116 case <-req.noChanges: 117 return []multiwatcher.Delta{}, nil 118 } 119 120 changes = req.changes 121 w.logger.Tracef("received %d changes", len(changes)) 122 if w.filter != nil { 123 changes = w.filter(changes) 124 if len(changes) == 0 { 125 w.logger.Tracef("filtered out all changes, looping") 126 } 127 } 128 } 129 w.logger.Tracef("returning %d changes", len(changes)) 130 return changes, nil 131 }