github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "time" 8 9 "github.com/juju/clock" 10 "github.com/juju/errors" 11 "gopkg.in/tomb.v2" 12 13 "github.com/juju/juju/apiserver/facade" 14 ) 15 16 // NewPinger returns an object that can be pinged by calling its Ping method. 17 // If this method is not called frequently enough, the connection will be 18 // dropped. 19 func NewPinger(ctx facade.Context) (Pinger, error) { 20 pingTimeout, ok := ctx.Resources().Get("pingTimeout").(*pingTimeout) 21 if !ok { 22 return nullPinger{}, nil 23 } 24 return pingTimeout, nil 25 } 26 27 // pinger describes a resource that can be pinged and stopped. 28 type Pinger interface { 29 Ping() 30 Stop() error 31 } 32 33 // pingTimeout listens for pings and will call the 34 // passed action in case of a timeout. This way broken 35 // or inactive connections can be closed. 36 type pingTimeout struct { 37 tomb tomb.Tomb 38 action func() 39 clock clock.Clock 40 timeout time.Duration 41 reset chan struct{} 42 } 43 44 // newPingTimeout returns a new pingTimeout instance 45 // that invokes the given action asynchronously if there 46 // is more than the given timeout interval between calls 47 // to its Ping method. 48 func newPingTimeout(action func(), clock clock.Clock, timeout time.Duration) Pinger { 49 pt := &pingTimeout{ 50 action: action, 51 clock: clock, 52 timeout: timeout, 53 reset: make(chan struct{}), 54 } 55 pt.tomb.Go(pt.loop) 56 return pt 57 } 58 59 // Ping is used by the client heartbeat monitor and resets 60 // the killer. 61 func (pt *pingTimeout) Ping() { 62 select { 63 case <-pt.tomb.Dying(): 64 case pt.reset <- struct{}{}: 65 } 66 } 67 68 // Stop terminates the ping timeout. 69 func (pt *pingTimeout) Stop() error { 70 pt.tomb.Kill(nil) 71 return pt.tomb.Wait() 72 } 73 74 // loop waits for a reset signal, otherwise it performs 75 // the initially passed action. 76 func (pt *pingTimeout) loop() error { 77 for { 78 select { 79 case <-pt.tomb.Dying(): 80 return tomb.ErrDying 81 case <-pt.reset: 82 case <-pt.clock.After(pt.timeout): 83 go pt.action() 84 return errors.New("ping timeout") 85 } 86 } 87 } 88 89 // nullPinger implements the pinger interface but just does nothing 90 type nullPinger struct{} 91 92 func (nullPinger) Ping() {} 93 func (nullPinger) Stop() error { return nil }