-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdependencies.go
196 lines (164 loc) · 5.25 KB
/
dependencies.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package libhealth
import (
"context"
"sync"
"time"
)
// BasicDependencySet is the standard implementation of DependancySet that
// behaves the way you would expect. HealthMonitors are registered to a
// DependencySet instance, and then you can call Live() or Background() on
// that instance. Live() will cause all of the HealthMonitors to call their
// Check() methods, whereas Background() will retrieve the cached Health
// of that health check.
type BasicDependencySet struct {
monitors map[string]HealthMonitor
cached map[string]Result
lock sync.RWMutex // locks the map structure, but not the values
ctx context.Context
initialRunWg sync.WaitGroup
}
// NewBasicDependencySet will create a new BasicDependencySet instance and
// register all of the provided HealthMonitor instances.
func NewBasicDependencySet(monitors ...HealthMonitor) *BasicDependencySet {
return NewBasicDependencySetWithContext(context.Background(), monitors...)
}
// NewBasicDependencySet will create a new BasicDependencySet instance using
// a specific context and register all of the provided HealthMonitor instances.
func NewBasicDependencySetWithContext(ctx context.Context, monitors ...HealthMonitor) *BasicDependencySet {
deps := &BasicDependencySet{
monitors: make(map[string]HealthMonitor),
cached: make(map[string]Result),
ctx: ctx,
}
deps.Register(monitors...)
return deps
}
// Register will register all of the provided HealthMonitor instances.
// HealthMonitors which have a 0 value Timeout will NOT be executed on
// Register(). They will only be executed on calls to Live(). This is important,
// because it means such a checker will be set to OUTAGE if only
// Background() is ever called.
func (d *BasicDependencySet) Register(monitors ...HealthMonitor) {
for _, monitor := range monitors {
notyetrun := fresh(monitor)
// set status not-run-yet
d.update(monitor, ¬yetrun)
d.initialRunWg.Add(1)
firstTime := func(monitor HealthMonitor) {
d.run(monitor, time.Now())
d.initialRunWg.Done()
}
// then run the check every period
if monitor.Period() > 0 {
go func(monitor HealthMonitor) {
// immediately run a check, then schedule more
firstTime(monitor)
// each healthcheck ticks and updates its associated health
// if the check times out, the health is set to outage
ticker := time.NewTicker(monitor.Period())
defer ticker.Stop()
for now := range ticker.C {
d.run(monitor, now)
}
}(monitor)
} else {
// then immediately kick off a check
go firstTime(monitor)
}
}
}
func (d *BasicDependencySet) waitUntilInitialRun() {
d.initialRunWg.Wait()
}
func (d *BasicDependencySet) run(monitor HealthMonitor, now time.Time) Result {
result := performCheck(d.ctx, monitor, now)
d.update(monitor, &result)
return result
}
func performCheck(ctx context.Context, monitor HealthMonitor, startTime time.Time) Result {
ctx, cancelFunc := context.WithDeadline(ctx, startTime.Add(monitor.Timeout()))
defer cancelFunc()
select {
case health := <-asyncCheck(ctx, monitor):
return wrap(monitor, health)
case <-ctx.Done():
return timeout(monitor, startTime)
}
}
func asyncCheck(ctx context.Context, monitor HealthMonitor) <-chan Health {
healthc := make(chan Health, 1)
go func() {
healthc <- monitor.Check(ctx)
}()
return healthc
}
func fresh(m HealthMonitor) Result {
h := NewHealth(OUTAGE, "healthcheck has not run yet")
h.Urgency = m.Urgency()
h.Time = time.Now()
return wrap(m, h)
}
func timeout(m HealthMonitor, t time.Time) Result {
h := NewHealth(OUTAGE, "healthcheck timed out")
h.Urgency = m.Urgency()
h.Time = t
return wrap(m, h)
}
func wrap(m HealthMonitor, h Health) Result {
// We don't care about the real state, just the downgraded one.
h.Status = h.Urgency.DowngradeWith(OK, h.Status)
return Result{
h,
m.Documentation(),
m.Description(),
m.LastOk(),
m.Period(),
m.Name(),
}
}
// Background will retrieve the cached Health for each of the registered
// HealthChecker instances.
func (d *BasicDependencySet) Background() Summary {
return NewSummary(time.Now(), d.snapshotResults())
}
// Live will force all of the HealthChecker instances to execute their
// Check methods, and will update all cached Health as well.
func (d *BasicDependencySet) Live() Summary {
monitors := d.snapshotMonitors()
checkResults := make(chan Result)
start := time.Now()
for _, monitor := range monitors {
go func(m HealthMonitor) {
checkResults <- d.run(m, start)
}(monitor)
}
results := make([]Result, 0, len(monitors))
for range monitors {
results = append(results, <-checkResults)
}
return NewSummary(time.Now(), results)
}
func (d *BasicDependencySet) update(monitor HealthMonitor, result *Result) {
d.lock.Lock()
defer d.lock.Unlock()
d.monitors[monitor.Name()] = monitor
d.cached[monitor.Name()] = *result
}
func (d *BasicDependencySet) snapshotMonitors() []HealthMonitor {
d.lock.RLock()
defer d.lock.RUnlock()
monitors := make([]HealthMonitor, 0, len(d.monitors))
for _, monitor := range d.monitors {
monitors = append(monitors, monitor)
}
return monitors
}
func (d *BasicDependencySet) snapshotResults() []Result {
d.lock.RLock()
defer d.lock.RUnlock()
results := make([]Result, 0, len(d.cached))
for _, result := range d.cached {
results = append(results, result)
}
return results
}