github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/singular/singular.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package singular
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/juju/loggo"
    12  
    13  	"github.com/juju/juju/worker"
    14  )
    15  
    16  var logger = loggo.GetLogger("juju.worker.singular")
    17  
    18  var PingInterval = 10 * time.Second
    19  
    20  type runner struct {
    21  	pingErr         error
    22  	pingerDied      chan struct{}
    23  	startPingerOnce sync.Once
    24  	isMaster        bool
    25  	worker.Runner
    26  	conn Conn
    27  }
    28  
    29  // Conn represents a connection to some resource.
    30  type Conn interface {
    31  	// IsMaster reports whether this connection is currently held by
    32  	// the (singular) master of the resource.
    33  	IsMaster() (bool, error)
    34  
    35  	// Ping probes the resource and returns an error if the the
    36  	// connection has failed. If the master changes, this method
    37  	// must return an error.
    38  	Ping() error
    39  }
    40  
    41  // New returns a Runner that can be used to start workers that will only
    42  // run a single instance. The conn value is used to determine whether to
    43  // run the workers or not.
    44  //
    45  // If conn.IsMaster returns true, any workers started will be started on the
    46  // underlying runner.
    47  //
    48  // If conn.IsMaster returns false, any workers started will actually
    49  // start do-nothing placeholder workers on the underlying runner
    50  // that continually ping the connection until a ping fails and then exit
    51  // with that error.
    52  func New(underlying worker.Runner, conn Conn) (worker.Runner, error) {
    53  	isMaster, err := conn.IsMaster()
    54  	if err != nil {
    55  		return nil, fmt.Errorf("cannot get master status: %v", err)
    56  	}
    57  	logger.Infof("runner created; isMaster %v", isMaster)
    58  	return &runner{
    59  		isMaster:   isMaster,
    60  		Runner:     underlying,
    61  		conn:       conn,
    62  		pingerDied: make(chan struct{}),
    63  	}, nil
    64  }
    65  
    66  // pinger periodically pings the connection to make sure that the
    67  // master-status has not changed. When the ping fails, it sets r.pingErr
    68  // to the error and closes r.pingerDied to signal the other workers to
    69  // quit.
    70  func (r *runner) pinger() {
    71  	underlyingDead := make(chan struct{})
    72  	go func() {
    73  		r.Runner.Wait()
    74  		close(underlyingDead)
    75  	}()
    76  	timer := time.NewTimer(0)
    77  	for {
    78  		if err := r.conn.Ping(); err != nil {
    79  			// The ping has failed: cause all other workers
    80  			// to exit with the ping error.
    81  			logger.Infof("pinger has died: %v", err)
    82  			r.pingErr = err
    83  			close(r.pingerDied)
    84  			return
    85  		}
    86  		timer.Reset(PingInterval)
    87  		select {
    88  		case <-timer.C:
    89  		case <-underlyingDead:
    90  			return
    91  		}
    92  	}
    93  }
    94  
    95  func (r *runner) StartWorker(id string, startFunc func() (worker.Worker, error)) error {
    96  	if r.isMaster {
    97  		// We are master; the started workers should
    98  		// encounter an error as they do what they're supposed
    99  		// to do - we can just start the worker in the
   100  		// underlying runner.
   101  		logger.Infof("starting %q", id)
   102  		return r.Runner.StartWorker(id, startFunc)
   103  	}
   104  	logger.Infof("standby %q", id)
   105  	// We're not master, so don't start the worker, but start a pinger so
   106  	// that we know when the connection master changes.
   107  	r.startPingerOnce.Do(func() {
   108  		go r.pinger()
   109  	})
   110  	return r.Runner.StartWorker(id, func() (worker.Worker, error) {
   111  		return worker.NewSimpleWorker(r.waitPinger), nil
   112  	})
   113  }
   114  
   115  func (r *runner) waitPinger(stop <-chan struct{}) error {
   116  	select {
   117  	case <-stop:
   118  		return nil
   119  	case <-r.pingerDied:
   120  		return r.pingErr
   121  	}
   122  }