github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/pinger.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver 5 6 import ( 7 "errors" 8 "time" 9 10 "launchpad.net/tomb" 11 12 "github.com/juju/juju/apiserver/common" 13 "github.com/juju/juju/state" 14 ) 15 16 func init() { 17 common.RegisterStandardFacade("Pinger", 1, NewPinger) 18 } 19 20 // NewPinger returns an object that can be pinged by calling its Ping method. 21 // If this method is not called frequently enough, the connection will be 22 // dropped. 23 func NewPinger(st *state.State, resources *common.Resources, authorizer common.Authorizer) (Pinger, error) { 24 pingTimeout, ok := resources.Get("pingTimeout").(*pingTimeout) 25 if !ok { 26 return nullPinger{}, nil 27 } 28 return pingTimeout, nil 29 } 30 31 // pinger describes a resource that can be pinged and stopped. 32 type Pinger interface { 33 Ping() 34 Stop() error 35 } 36 37 // pingTimeout listens for pings and will call the 38 // passed action in case of a timeout. This way broken 39 // or inactive connections can be closed. 40 type pingTimeout struct { 41 tomb tomb.Tomb 42 action func() 43 timeout time.Duration 44 reset chan time.Duration 45 } 46 47 // newPingTimeout returns a new pingTimeout instance 48 // that invokes the given action asynchronously if there 49 // is more than the given timeout interval between calls 50 // to its Ping method. 51 func newPingTimeout(action func(), timeout time.Duration) Pinger { 52 pt := &pingTimeout{ 53 action: action, 54 timeout: timeout, 55 reset: make(chan time.Duration), 56 } 57 go func() { 58 defer pt.tomb.Done() 59 pt.tomb.Kill(pt.loop()) 60 }() 61 return pt 62 } 63 64 // Ping is used by the client heartbeat monitor and resets 65 // the killer. 66 func (pt *pingTimeout) Ping() { 67 select { 68 case <-pt.tomb.Dying(): 69 case pt.reset <- pt.timeout: 70 } 71 } 72 73 // Stop terminates the ping timeout. 74 func (pt *pingTimeout) Stop() error { 75 pt.tomb.Kill(nil) 76 return pt.tomb.Wait() 77 } 78 79 // loop waits for a reset signal, otherwise it performs 80 // the initially passed action. 81 func (pt *pingTimeout) loop() error { 82 // TODO(fwereade): 2016-03-17 lp:1558657 83 timer := time.NewTimer(pt.timeout) 84 defer timer.Stop() 85 for { 86 select { 87 case <-pt.tomb.Dying(): 88 return nil 89 case <-timer.C: 90 go pt.action() 91 return errors.New("ping timeout") 92 case duration := <-pt.reset: 93 timer.Reset(duration) 94 } 95 } 96 } 97 98 // nullPinger implements the pinger interface but just does nothing 99 type nullPinger struct{} 100 101 func (nullPinger) Ping() {} 102 func (nullPinger) Stop() error { return nil }