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