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 }