github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  	for timer := time.NewTimer(PingInterval); ; timer.Reset(PingInterval) {
    77  		if err := r.conn.Ping(); err != nil {
    78  			// The ping has failed: cause all other workers
    79  			// to exit with the ping error.
    80  			logger.Infof("pinger has died: %v", err)
    81  			r.pingErr = err
    82  			close(r.pingerDied)
    83  			return
    84  		}
    85  
    86  		select {
    87  		case <-timer.C:
    88  		case <-underlyingDead:
    89  			return
    90  		}
    91  	}
    92  }
    93  
    94  func (r *runner) StartWorker(id string, startFunc func() (worker.Worker, error)) error {
    95  	if r.isMaster {
    96  		// We are master; the started workers should
    97  		// encounter an error as they do what they're supposed
    98  		// to do - we can just start the worker in the
    99  		// underlying runner.
   100  		logger.Infof("starting %q", id)
   101  		return r.Runner.StartWorker(id, startFunc)
   102  	}
   103  	logger.Infof("standby %q", id)
   104  	// We're not master, so don't start the worker, but start a pinger so
   105  	// that we know when the connection master changes.
   106  	r.startPingerOnce.Do(func() {
   107  		go r.pinger()
   108  	})
   109  	return r.Runner.StartWorker(id, func() (worker.Worker, error) {
   110  		return worker.NewSimpleWorker(r.waitPinger), nil
   111  	})
   112  }
   113  
   114  func (r *runner) waitPinger(stop <-chan struct{}) error {
   115  	select {
   116  	case <-stop:
   117  		return nil
   118  	case <-r.pingerDied:
   119  		return r.pingErr
   120  	}
   121  }