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 }