github.com/xmidt-org/webpa-common@v1.11.9/xhttp/gate/gate.go (about)

     1  package gate
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/go-kit/kit/metrics/discard"
     9  	"github.com/xmidt-org/webpa-common/xmetrics"
    10  )
    11  
    12  const (
    13  	// Open is the value a gauge is set to that indicates the gate is open
    14  	Open float64 = 1.0
    15  
    16  	// Closed is the value a gauge is set to that indicates the gate is closed
    17  	Closed float64 = 0.0
    18  )
    19  
    20  // Interface represents a concurrent condition indicating whether HTTP traffic should be allowed.
    21  // This type essentially represents an atomic boolean with some extra functionality, such as metrics gathering.
    22  type Interface interface {
    23  	fmt.Stringer
    24  
    25  	// Raise opens this gate.  If the gate was raised as a result, this method returns true.  If the
    26  	// gate was already raised, this method returns false.
    27  	Raise() bool
    28  
    29  	// Lower closes this gate.  If the gate was lowered as a result, this method returns true.  If the
    30  	// gate was already lowered, this method returns false.
    31  	Lower() bool
    32  
    33  	// Open tests if this gate is open
    34  	Open() bool
    35  
    36  	// State returns the current state (true for open, false for closed) along with the time
    37  	// at which this gate entered that state.
    38  	State() (bool, time.Time)
    39  }
    40  
    41  // GateOption is a configuration option for a gate Interface
    42  type GateOption func(*gate)
    43  
    44  // WithGauge configures a gate with a metrics Gauge that tracks the state of the gate.
    45  func WithGauge(gauge xmetrics.Setter) GateOption {
    46  	return func(g *gate) {
    47  		if gauge != nil {
    48  			g.state = gauge
    49  		} else {
    50  			g.state = discard.NewGauge()
    51  		}
    52  	}
    53  }
    54  
    55  // New constructs a gate Interface with zero or more options.  The returned gate takes on the given
    56  // initial state, and any configured gauge is updated to reflect this initial state.
    57  func New(initial bool, options ...GateOption) Interface {
    58  	g := &gate{
    59  		open:  initial,
    60  		now:   time.Now,
    61  		state: discard.NewGauge(),
    62  	}
    63  
    64  	for _, o := range options {
    65  		o(g)
    66  	}
    67  
    68  	if g.open {
    69  		g.state.Set(Open)
    70  	} else {
    71  		g.state.Set(Closed)
    72  	}
    73  
    74  	g.timestamp = g.now().UTC()
    75  	return g
    76  }
    77  
    78  // gate is the internal Interface implementation
    79  type gate struct {
    80  	lock      sync.RWMutex
    81  	open      bool
    82  	timestamp time.Time
    83  	now       func() time.Time
    84  
    85  	state xmetrics.Setter
    86  }
    87  
    88  func (g *gate) Raise() bool {
    89  	defer g.lock.Unlock()
    90  	g.lock.Lock()
    91  
    92  	if g.open {
    93  		return false
    94  	}
    95  
    96  	g.open = true
    97  	g.state.Set(Open)
    98  	g.timestamp = g.now().UTC()
    99  	return true
   100  }
   101  
   102  func (g *gate) Lower() bool {
   103  	defer g.lock.Unlock()
   104  	g.lock.Lock()
   105  
   106  	if !g.open {
   107  		return false
   108  	}
   109  
   110  	g.open = false
   111  	g.state.Set(Closed)
   112  	g.timestamp = g.now().UTC()
   113  	return true
   114  }
   115  
   116  func (g *gate) Open() bool {
   117  	g.lock.RLock()
   118  	open := g.open
   119  	g.lock.RUnlock()
   120  
   121  	return open
   122  }
   123  
   124  func (g *gate) State() (bool, time.Time) {
   125  	g.lock.RLock()
   126  	open := g.open
   127  	timestamp := g.timestamp
   128  	g.lock.RUnlock()
   129  
   130  	return open, timestamp
   131  }
   132  
   133  func (g *gate) String() string {
   134  	if g.Open() {
   135  		return "open"
   136  	} else {
   137  		return "closed"
   138  	}
   139  }