-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathrecycle.go
130 lines (116 loc) · 3.22 KB
/
recycle.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
package transport
import (
"math/rand"
"net/http"
"sync"
"time"
)
// Recycler is a decorator that discards and regenerates the transport after
// a given set of triggers.
type Recycler struct {
wrapped http.RoundTripper
ttl time.Duration
ttlJitter time.Duration
nextTTL time.Time
maxUsage int
currentUsage int
signals []chan struct{}
signal chan struct{}
lock *sync.Mutex
factory Factory
}
// RecycleOption is a configuration for the Recycler decorator
type RecycleOption func(*Recycler) *Recycler
// RecycleOptionTTL configures the recycler to rotate Transports on an interval.
func RecycleOptionTTL(ttl time.Duration) RecycleOption {
return func(r *Recycler) *Recycler {
r.ttl = ttl
return r
}
}
// RecycleOptionTTLJitter adds a randomized jitter to the TTL that is plus or
// minus the duration value given.
func RecycleOptionTTLJitter(jitter time.Duration) RecycleOption {
return func(r *Recycler) *Recycler {
r.ttlJitter = jitter
return r
}
}
// RecycleOptionMaxUsage configures the recycler to rotate Transports after a number
// of uses.
func RecycleOptionMaxUsage(max int) RecycleOption {
return func(r *Recycler) *Recycler {
r.maxUsage = max
return r
}
}
// RecycleOptionChannel configures the recycler to rotate based on input from a
// channel.
func RecycleOptionChannel(signal chan struct{}) RecycleOption {
return func(r *Recycler) *Recycler {
r.signals = append(r.signals, signal)
return r
}
}
// NewRecycler uses the given factory as a source and recycles the transport
// based on the options given.
func NewRecycler(factory Factory, opts ...RecycleOption) *Recycler {
var r = &Recycler{wrapped: factory(), lock: &sync.Mutex{}, factory: factory, signal: make(chan struct{})}
for _, opt := range opts {
r = opt(r)
}
r.listen()
return r
}
// NewRecyclerFactory is a counterpart for NewRecycler that generates a Factory
// function for use with other decorators.
func NewRecyclerFactory(factory Factory, opts ...RecycleOption) Factory {
return func() http.RoundTripper {
return NewRecycler(factory, opts...)
}
}
func (c *Recycler) resetTransport() http.RoundTripper {
c.wrapped = c.factory()
c.currentUsage = 0
var renderedJitter = time.Duration(rand.Float64() * float64(c.ttlJitter)) // nolint:gosec
if rand.Float64()*100 > 50 { // nolint:gosec
renderedJitter = -renderedJitter
}
c.nextTTL = time.Now().Add(c.ttl + renderedJitter)
return c.wrapped
}
func (c *Recycler) listen() {
for _, signal := range c.signals {
go c.listenOne(signal)
}
}
func (c *Recycler) listenOne(s chan struct{}) {
for range s {
c.signal <- struct{}{}
}
}
func (c *Recycler) getTransport() http.RoundTripper {
c.lock.Lock()
defer c.lock.Unlock()
if c.maxUsage > 0 {
c.currentUsage = c.currentUsage + 1
if c.currentUsage > c.maxUsage {
return c.resetTransport()
}
}
if c.ttl > 0 && time.Now().After(c.nextTTL) {
return c.resetTransport()
}
select {
case <-c.signal:
return c.resetTransport()
default:
break
}
return c.wrapped
}
// RoundTrip applies the discard and regenerate policy.
func (c *Recycler) RoundTrip(r *http.Request) (*http.Response, error) {
var rt = c.getTransport()
return rt.RoundTrip(r)
}