-
Notifications
You must be signed in to change notification settings - Fork 682
/
limiter.go
111 lines (99 loc) · 3.16 KB
/
limiter.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
package limiter
import "time"
// A limiter can be used to rate limit and/or coalesce a series of
// time-based events. This interface captures the logic of deciding
// what action should be taken when an event occurs at a specific time
// T. The possible actions are act upon the event, do nothing, or
// check back after a specific delay.
type Limiter interface {
// The Limit() method works kinda like an 8 ball. You pass it
// the time of an event, and it returns one of Yes, No, or
// "Later". A zero return value means "Yes", the event should
// be acted upon right away. A negative return value means
// "No", do nothing, and a positive return value means to
// check back after the returned delay.
//
// The order that this is invoked is important, and it is
// expected that this is invoked with a monotonically
// increasing set of timestamps. Typically this will be
// invoked when an event is generated and will be the result
// of time.Now(), but for testing or other cases, this could
// be invoked with any set of historic or future timestamps so
// long as they are invoked in monotonically increasing order.
//
// The result of this (when positive) is always relative to
// the passed in time. In other words to compute a deadline
// rather than a delay, you should take the result of Limit()
// and add it to whatever value you passed in:
//
// deadline = now + Limit(now).
//
Limit(now time.Time) time.Duration
}
type limiter struct {
// we should never fire more than one event within this timespan
interval time.Duration
// records the last event we fired
lastAction time.Time
// records the point in the future to which we delayed an event
deadline time.Time
}
// Constructs a new limiter that will coalesce any events occurring
// within the specified interval.
func NewInterval(interval time.Duration) Limiter {
return &limiter{
interval: interval,
}
}
func (l *limiter) Limit(now time.Time) time.Duration {
since := now.Sub(l.lastAction)
switch {
case since >= l.interval:
// we haven't triggered any event recently, so we
// trigger this event and record that we did so
l.lastAction = now
return 0
case l.deadline.After(now):
// we are still waiting for an event that we delayed
// into the future, so we can drop this one
return -1
default:
// we have multiple events within the same interval,
// and there are no delayed events, so we delay this
// one and record the point in the future to which we
// delayed it
delay := l.interval - since
l.deadline = now.Add(delay)
return delay
}
}
type composite struct {
first Limiter
second Limiter
delay time.Duration
started bool
deadline time.Time
}
func NewComposite(first, second Limiter, delay time.Duration) Limiter {
return &composite{
first: first,
second: second,
delay: delay,
}
}
func (c *composite) Limit(now time.Time) time.Duration {
if !c.started {
c.started = true
c.deadline = now.Add(c.delay)
}
if now.After(c.deadline) {
return c.second.Limit(now)
} else {
return c.first.Limit(now)
}
}
type unlimited struct{}
func NewUnlimited() Limiter {
return &unlimited{}
}
func (u *unlimited) Limit(now time.Time) time.Duration { return 0 }