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 }