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