-
Notifications
You must be signed in to change notification settings - Fork 55
/
rule.go
171 lines (157 loc) · 4.33 KB
/
rule.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
package limiter
import (
"sync"
"time"
)
type singleRule struct {
defaultExpiration time.Duration
cleanupInterval time.Duration
allowed int
estimated int
records []*circleQueue
notRecordsIndex map[int]struct{}
usedRecordsIndex sync.Map
locker *sync.Mutex
}
// newRule Initialize an access control policy
func newRule(defaultExpiration time.Duration, allowed int, estimated ...int) *singleRule {
if allowed <= 0 {
allowed = 1
}
userEstimated := 0
if len(estimated) > 0 {
userEstimated = estimated[0]
}
if userEstimated <= 0 {
userEstimated = allowed
}
cleanupInterval := defaultExpiration / 100
if cleanupInterval < time.Second*1 {
cleanupInterval = time.Second * 1
}
if cleanupInterval > time.Second*60 {
cleanupInterval = time.Second * 60
}
vc := createRule(defaultExpiration, cleanupInterval, allowed, userEstimated)
go vc.deleteExpired()
return vc
}
func createRule(defaultExpiration, cleanupInterval time.Duration, allowed, userEstimated int) *singleRule {
var vc singleRule
var locker sync.Mutex
vc.defaultExpiration = defaultExpiration
vc.cleanupInterval = cleanupInterval
vc.allowed = allowed
vc.estimated = userEstimated
vc.notRecordsIndex = make(map[int]struct{})
vc.locker = &locker
vc.records = make([]*circleQueue, vc.estimated)
for i := range vc.records {
vc.records[i] = newCircleQueue(vc.allowed)
vc.notRecordsIndex[i] = struct{}{}
}
return &vc
}
// allowVisit Whether access is allowed or not. If access is allowed, an access record is added to the access record
func (r *singleRule) allowVisit(key interface{}) bool {
return r.add(key) == nil
}
// remainingVisits Remaining visits
func (r *singleRule) remainingVisits(key interface{}) int {
if index, exist := r.usedRecordsIndex.Load(key); exist {
r.records[index.(int)].deleteExpired()
return r.records[index.(int)].unUsedSize()
}
return r.allowed
}
// add access record
func (r *singleRule) add(key interface{}) (err error) {
r.locker.Lock()
defer r.locker.Unlock()
if index, exist := r.usedRecordsIndex.Load(key); exist {
r.records[index.(int)].deleteExpired()
return r.records[index.(int)].push(time.Now().Add(r.defaultExpiration).UnixNano())
}
if len(r.notRecordsIndex) > 0 {
for index := range r.notRecordsIndex {
delete(r.notRecordsIndex, index)
r.usedRecordsIndex.Store(key, index)
return r.records[index].push(time.Now().Add(r.defaultExpiration).UnixNano())
}
}
queue := newCircleQueue(r.allowed)
r.records = append(r.records, queue)
index := len(r.records) - 1
r.usedRecordsIndex.Store(key, index)
return r.records[index].push(time.Now().Add(r.defaultExpiration).UnixNano())
}
// deleteExpired Delete expired data
func (r *singleRule) deleteExpired() {
for range time.Tick(r.cleanupInterval) {
r.deleteExpiredOnce()
r.recovery()
}
}
// deleteExpiredOnce Delete expired data once in a specific time interval
func (r *singleRule) deleteExpiredOnce() {
r.usedRecordsIndex.Range(func(k, v interface{}) bool {
r.locker.Lock()
index := v.(int)
if index < len(r.records) && index >= 0 {
r.records[index].deleteExpired()
if r.records[index].usedSize() == 0 {
r.usedRecordsIndex.Delete(k)
r.notRecordsIndex[index] = struct{}{}
}
} else {
r.usedRecordsIndex.Delete(k)
}
r.locker.Unlock()
return true
})
}
func (r *singleRule) recovery() {
r.locker.Lock()
defer r.locker.Unlock()
if r.needRecovery() {
curLen := len(r.records)
unUsedLen := len(r.notRecordsIndex)
usedLen := curLen - unUsedLen
var newLen int
if usedLen < r.estimated {
newLen = r.estimated
} else {
newLen = usedLen * 2
}
visitorRecordsNew := make([]*circleQueue, newLen)
for i := range visitorRecordsNew {
visitorRecordsNew[i] = newCircleQueue(r.allowed)
}
r.notRecordsIndex = make(map[int]struct{})
indexNew := 0
r.usedRecordsIndex.Range(func(k, v interface{}) bool {
indexOld := v.(int)
visitorRecordsNew[indexNew] = r.records[indexOld]
indexNew++
return true
})
r.records = visitorRecordsNew
for index := range r.records {
if index >= indexNew {
r.notRecordsIndex[index] = struct{}{}
}
}
}
}
func (r *singleRule) needRecovery() bool {
curLen := len(r.records)
unUsedLen := len(r.notRecordsIndex)
usedLen := curLen - unUsedLen
if curLen < 2*r.estimated {
return false
}
if usedLen*2 < unUsedLen {
return true
}
return false
}