github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/watcher/legacy/notifyworker.go (about)

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package legacy
     5  
     6  import (
     7  	"gopkg.in/juju/worker.v1"
     8  	"gopkg.in/tomb.v2"
     9  
    10  	"github.com/juju/juju/state"
    11  	"github.com/juju/juju/state/watcher"
    12  )
    13  
    14  // ensureErr is defined as a variable to allow the test suite
    15  // to override it.
    16  var ensureErr = watcher.EnsureErr
    17  
    18  // notifyWorker is the internal implementation of the Worker
    19  // interface, using a NotifyWatcher for handling changes.
    20  type notifyWorker struct {
    21  	tomb    tomb.Tomb
    22  	handler NotifyWatchHandler
    23  }
    24  
    25  // NotifyWatchHandler implements the business logic that is triggered
    26  // as part of watching a NotifyWatcher.
    27  type NotifyWatchHandler interface {
    28  	// SetUp will be called once, and should return the watcher that will
    29  	// be used to trigger subsequent Handle()s. SetUp can return a watcher
    30  	// even if there is an error, and the notify worker will make sure
    31  	// to stop the watcher.
    32  	SetUp() (state.NotifyWatcher, error)
    33  
    34  	// TearDown should cleanup any resources that are left around.
    35  	TearDown() error
    36  
    37  	// Handle is called whenever the watcher returned from SetUp sends a value
    38  	// on its Changes() channel. The done channel will be closed if and when
    39  	// the worker is being interrupted to finish. Any worker should avoid any
    40  	// bare channel reads or writes, but instead use a select with the done
    41  	// channel.
    42  	Handle(done <-chan struct{}) error
    43  }
    44  
    45  // NewNotifyWorker starts a new worker running the business logic from
    46  // the handler. The worker loop is started in another goroutine as a
    47  // side effect of calling this.
    48  func NewNotifyWorker(handler NotifyWatchHandler) worker.Worker {
    49  	nw := &notifyWorker{
    50  		handler: handler,
    51  	}
    52  
    53  	nw.tomb.Go(nw.loop)
    54  	return nw
    55  }
    56  
    57  // Kill is part of the worker.Worker interface.
    58  func (nw *notifyWorker) Kill() {
    59  	nw.tomb.Kill(nil)
    60  }
    61  
    62  // Wait is part of the worker.Worker interface.
    63  func (nw *notifyWorker) Wait() error {
    64  	return nw.tomb.Wait()
    65  }
    66  
    67  type tearDowner interface {
    68  	TearDown() error
    69  }
    70  
    71  // propagateTearDown tears down the handler, but ensures any error is
    72  // propagated through the tomb's Kill method.
    73  func propagateTearDown(handler tearDowner, t *tomb.Tomb) {
    74  	if err := handler.TearDown(); err != nil {
    75  		t.Kill(err)
    76  	}
    77  }
    78  
    79  func (nw *notifyWorker) loop() error {
    80  	w, err := nw.handler.SetUp()
    81  	if err != nil {
    82  		if w != nil {
    83  			// We don't bother to propagate an error, because we
    84  			// already have an error
    85  			w.Stop()
    86  		}
    87  		return err
    88  	}
    89  	defer propagateTearDown(nw.handler, &nw.tomb)
    90  	defer watcher.Stop(w, &nw.tomb)
    91  	for {
    92  		select {
    93  		case <-nw.tomb.Dying():
    94  			return tomb.ErrDying
    95  		case _, ok := <-w.Changes():
    96  			if !ok {
    97  				return ensureErr(w)
    98  			}
    99  			if err := nw.handler.Handle(nw.tomb.Dying()); err != nil {
   100  				return err
   101  			}
   102  		}
   103  	}
   104  }