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

     1  // Copyright 2013-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package watcher
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/worker/v3"
     9  	"github.com/juju/worker/v3/catacomb"
    10  )
    11  
    12  // StringsChannel is a change channel as described in the CoreWatcher docs.
    13  //
    14  // It sends a single value indicating a baseline set of values, and subsequent
    15  // values representing additions, changes, and/or removals of those values. The
    16  // precise semantics may depend upon the individual watcher.
    17  type StringsChannel <-chan []string
    18  
    19  // StringsWatcher conveniently ties a StringsChannel to the worker.Worker that
    20  // represents its validity.
    21  type StringsWatcher interface {
    22  	CoreWatcher
    23  	Changes() StringsChannel
    24  }
    25  
    26  // StringsHandler defines the operation of a StringsWorker.
    27  type StringsHandler interface {
    28  
    29  	// SetUp is called once when creating a StringsWorker. It must return a
    30  	// StringsWatcher or an error. The StringsHandler takes responsibility for
    31  	// stopping any returned watcher and handling any errors.
    32  	SetUp() (StringsWatcher, error)
    33  
    34  	// Handle is called with every value received from the StringsWatcher
    35  	// returned by SetUp. If it returns an error, the StringsWorker will be
    36  	// stopped.
    37  	//
    38  	// If Handle runs any blocking operations it must pass through, or select
    39  	// on, the supplied abort channel; this channel will be closed when the
    40  	// StringsWorker is killed. An aborted Handle should not return an error.
    41  	Handle(abort <-chan struct{}, changes []string) error
    42  
    43  	// TearDown is called once when stopping a StringsWorker, whether or not
    44  	// SetUp succeeded. It need not concern itself with the StringsWatcher, but
    45  	// must clean up any other resources created in SetUp or Handle.
    46  	TearDown() error
    47  }
    48  
    49  // StringsConfig holds the direct dependencies of a StringsWorker.
    50  type StringsConfig struct {
    51  	Handler StringsHandler
    52  }
    53  
    54  // Validate returns ann error if the config cannot start a StringsWorker.
    55  func (config StringsConfig) Validate() error {
    56  	if config.Handler == nil {
    57  		return errors.NotValidf("nil Handler")
    58  	}
    59  	return nil
    60  }
    61  
    62  // NewStringsWorker starts a new worker that runs a StringsHandler.
    63  func NewStringsWorker(config StringsConfig) (*StringsWorker, error) {
    64  	if err := config.Validate(); err != nil {
    65  		return nil, errors.Trace(err)
    66  	}
    67  	sw := &StringsWorker{
    68  		config: config,
    69  	}
    70  	err := catacomb.Invoke(catacomb.Plan{
    71  		Site: &sw.catacomb,
    72  		Work: sw.loop,
    73  	})
    74  	if err != nil {
    75  		return nil, errors.Trace(err)
    76  	}
    77  	return sw, nil
    78  }
    79  
    80  // StringsWorker is a worker that wraps a StringsWatcher.
    81  type StringsWorker struct {
    82  	config   StringsConfig
    83  	catacomb catacomb.Catacomb
    84  }
    85  
    86  func (sw *StringsWorker) loop() (err error) {
    87  	changes := sw.setUp()
    88  	defer sw.tearDown(err)
    89  	abort := sw.catacomb.Dying()
    90  	for {
    91  		select {
    92  		case <-abort:
    93  			return sw.catacomb.ErrDying()
    94  		case strings, ok := <-changes:
    95  			if !ok {
    96  				return errors.New("change channel closed")
    97  			}
    98  			err = sw.config.Handler.Handle(abort, strings)
    99  			if err != nil {
   100  				return err
   101  			}
   102  		}
   103  	}
   104  }
   105  
   106  // setUp calls the handler's SetUp method; registers any returned watcher with
   107  // the worker's catacomb; and returns the watcher's changes channel. Any errors
   108  // encountered kill the worker and cause a nil channel to be returned.
   109  func (sw *StringsWorker) setUp() StringsChannel {
   110  	watcher, err := sw.config.Handler.SetUp()
   111  	if err != nil {
   112  		sw.catacomb.Kill(err)
   113  	}
   114  	if watcher == nil {
   115  		sw.catacomb.Kill(errors.New("handler returned nil watcher"))
   116  	} else {
   117  		if err := sw.catacomb.Add(watcher); err != nil {
   118  			sw.catacomb.Kill(err)
   119  		} else {
   120  			return watcher.Changes()
   121  		}
   122  	}
   123  	return nil
   124  }
   125  
   126  // tearDown kills the worker with the supplied error; and then kills it with
   127  // any error returned by the handler's TearDown method.
   128  func (sw *StringsWorker) tearDown(err error) {
   129  	sw.catacomb.Kill(err)
   130  	err = sw.config.Handler.TearDown()
   131  	sw.catacomb.Kill(err)
   132  }
   133  
   134  // Kill is part of the worker.Worker interface.
   135  func (sw *StringsWorker) Kill() {
   136  	sw.catacomb.Kill(nil)
   137  }
   138  
   139  // Wait is part of the worker.Worker interface.
   140  func (sw *StringsWorker) Wait() error {
   141  	return sw.catacomb.Wait()
   142  }
   143  
   144  // Report implements dependency.Reporter.
   145  func (sw *StringsWorker) Report() map[string]interface{} {
   146  	if r, ok := sw.config.Handler.(worker.Reporter); ok {
   147  		return r.Report()
   148  	}
   149  	return nil
   150  }