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  }