-
Notifications
You must be signed in to change notification settings - Fork 649
/
adaptive_timeout_manager.go
231 lines (191 loc) · 6.34 KB
/
adaptive_timeout_manager.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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package timer
import (
"container/heap"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/ava-labs/avalanchego/ids"
)
type adaptiveTimeout struct {
index int // Index in the wait queue
id ids.ID // Unique ID of this timeout
handler func() // Function to execute if timed out
duration time.Duration // How long this timeout was set for
deadline time.Time // When this timeout should be fired
}
// A timeoutQueue implements heap.Interface and holds adaptiveTimeouts.
type timeoutQueue []*adaptiveTimeout
func (tq timeoutQueue) Len() int { return len(tq) }
func (tq timeoutQueue) Less(i, j int) bool { return tq[i].deadline.Before(tq[j].deadline) }
func (tq timeoutQueue) Swap(i, j int) {
tq[i], tq[j] = tq[j], tq[i]
tq[i].index = i
tq[j].index = j
}
// Push adds an item to this priority queue. x must have type *adaptiveTimeout
func (tq *timeoutQueue) Push(x interface{}) {
item := x.(*adaptiveTimeout)
item.index = len(*tq)
*tq = append(*tq, item)
}
// Pop returns the next item in this queue
func (tq *timeoutQueue) Pop() interface{} {
n := len(*tq)
item := (*tq)[n-1]
(*tq)[n-1] = nil // make sure the item is freed from memory
*tq = (*tq)[:n-1]
return item
}
// AdaptiveTimeoutConfig contains the parameters that should be provided to the
// adaptive timeout manager.
type AdaptiveTimeoutConfig struct {
InitialTimeout time.Duration
MinimumTimeout time.Duration
MaximumTimeout time.Duration
TimeoutMultiplier float64
TimeoutReduction time.Duration
Namespace string
Registerer prometheus.Registerer
}
// AdaptiveTimeoutManager is a manager for timeouts.
type AdaptiveTimeoutManager struct {
currentDurationMetric prometheus.Gauge
minimumTimeout time.Duration
maximumTimeout time.Duration
timeoutMultiplier float64
timeoutReduction time.Duration
lock sync.Mutex
currentTimeout time.Duration // Amount of time before a timeout
timeoutMap map[[32]byte]*adaptiveTimeout
timeoutQueue timeoutQueue
timer *Timer // Timer that will fire to clear the timeouts
}
// Initialize this timeout manager with the provided config
func (tm *AdaptiveTimeoutManager) Initialize(config *AdaptiveTimeoutConfig) error {
tm.currentDurationMetric = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: config.Namespace,
Name: "network_timeout",
Help: "Duration of current network timeouts in nanoseconds",
})
tm.minimumTimeout = config.MinimumTimeout
tm.maximumTimeout = config.MaximumTimeout
tm.timeoutMultiplier = config.TimeoutMultiplier
tm.timeoutReduction = config.TimeoutReduction
tm.currentTimeout = config.InitialTimeout
tm.timeoutMap = make(map[[32]byte]*adaptiveTimeout)
tm.timer = NewTimer(tm.Timeout)
return config.Registerer.Register(tm.currentDurationMetric)
}
// Dispatch ...
func (tm *AdaptiveTimeoutManager) Dispatch() { tm.timer.Dispatch() }
// Stop executing timeouts
func (tm *AdaptiveTimeoutManager) Stop() { tm.timer.Stop() }
// Put puts hash into the hash map
func (tm *AdaptiveTimeoutManager) Put(id ids.ID, handler func()) time.Time {
tm.lock.Lock()
defer tm.lock.Unlock()
return tm.put(id, handler)
}
// Remove the item that no longer needs to be there.
func (tm *AdaptiveTimeoutManager) Remove(id ids.ID) {
tm.lock.Lock()
defer tm.lock.Unlock()
currentTime := time.Now()
tm.remove(id, currentTime)
}
// Timeout registers a timeout
func (tm *AdaptiveTimeoutManager) Timeout() {
tm.lock.Lock()
defer tm.lock.Unlock()
tm.timeout()
}
func (tm *AdaptiveTimeoutManager) timeout() {
currentTime := time.Now()
// removeExpiredHead returns nil once there is nothing left to remove
for {
timeout := tm.removeExpiredHead(currentTime)
if timeout == nil {
break
}
// Don't execute a callback with a lock held
tm.lock.Unlock()
timeout()
tm.lock.Lock()
}
tm.registerTimeout()
}
func (tm *AdaptiveTimeoutManager) put(id ids.ID, handler func()) time.Time {
currentTime := time.Now()
tm.remove(id, currentTime)
timeout := &adaptiveTimeout{
id: id,
handler: handler,
duration: tm.currentTimeout,
deadline: currentTime.Add(tm.currentTimeout),
}
tm.timeoutMap[id.Key()] = timeout
heap.Push(&tm.timeoutQueue, timeout)
tm.registerTimeout()
return timeout.deadline
}
func (tm *AdaptiveTimeoutManager) remove(id ids.ID, currentTime time.Time) {
key := id.Key()
timeout, exists := tm.timeoutMap[key]
if !exists {
return
}
if timeout.deadline.Before(currentTime) {
// This request is being removed because it timed out.
if timeout.duration >= tm.currentTimeout {
// If the current timeout duration is less than or equal to the
// timeout that was triggered, double the duration.
tm.currentTimeout = time.Duration(float64(tm.currentTimeout) * tm.timeoutMultiplier)
if tm.currentTimeout > tm.maximumTimeout {
// Make sure that we never get stuck in a bad situation
tm.currentTimeout = tm.maximumTimeout
}
}
} else {
// This request is being removed because it finished successfully.
if timeout.duration <= tm.currentTimeout {
// If the current timeout duration is greater than or equal to the
// timeout that was fullfilled, reduce future timeouts.
tm.currentTimeout -= tm.timeoutReduction
if tm.currentTimeout < tm.minimumTimeout {
// Make sure that we never get stuck in a bad situation
tm.currentTimeout = tm.minimumTimeout
}
}
}
// Make sure the metrics report the current timeouts
tm.currentDurationMetric.Set(float64(tm.currentTimeout))
// Remove the timeout from the map
delete(tm.timeoutMap, key)
// Remove the timeout from the queue
heap.Remove(&tm.timeoutQueue, timeout.index)
}
// Returns true if the head was removed, false otherwise
func (tm *AdaptiveTimeoutManager) removeExpiredHead(currentTime time.Time) func() {
if tm.timeoutQueue.Len() == 0 {
return nil
}
nextTimeout := tm.timeoutQueue[0]
if nextTimeout.deadline.After(currentTime) {
return nil
}
tm.remove(nextTimeout.id, currentTime)
return nextTimeout.handler
}
func (tm *AdaptiveTimeoutManager) registerTimeout() {
if tm.timeoutQueue.Len() == 0 {
// There are no pending timeouts
tm.timer.Cancel()
return
}
currentTime := time.Now()
nextTimeout := tm.timeoutQueue[0]
timeToNextTimeout := nextTimeout.deadline.Sub(currentTime)
tm.timer.SetTimeoutIn(timeToNextTimeout)
}