github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/notifyworker.go (about)

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