main
go 199 lines 3.63 KB
Raw
1 package policy
2
3 import (
4 "sync"
5 "time"
6
7 "github.com/gosuda/portal-tunnel/v2/utils"
8 )
9
10 type BPSManager struct {
11 identityBPS *utils.Snapshot[map[string]int64]
12 identityLimiters map[string]*bpsLimiter
13 mu sync.RWMutex
14 }
15
16 func NewBPSManager() *BPSManager {
17 return &BPSManager{
18 identityBPS: utils.NewSnapshot(map[string]int64{}, utils.CloneMap[string, int64]),
19 identityLimiters: make(map[string]*bpsLimiter),
20 }
21 }
22
23 func (m *BPSManager) IdentityBPS(key string) int64 {
24 if m == nil || key == "" {
25 return 0
26 }
27 if m.identityBPS == nil {
28 return 0
29 }
30 return m.identityBPS.Load()[key]
31 }
32
33 func (m *BPSManager) SetIdentityBPS(key string, bps int64) {
34 if m == nil || key == "" {
35 return
36 }
37
38 if m.identityBPS == nil {
39 return
40 }
41 if bps <= 0 {
42 m.identityBPS.UpdateCopy(func(limits *map[string]int64) {
43 delete(*limits, key)
44 })
45 m.mu.Lock()
46 delete(m.identityLimiters, key)
47 m.mu.Unlock()
48 return
49 }
50 m.identityBPS.UpdateCopy(func(limits *map[string]int64) {
51 if *limits == nil {
52 *limits = make(map[string]int64)
53 }
54 (*limits)[key] = bps
55 })
56 }
57
58 func (m *BPSManager) DeleteIdentityBPS(key string) {
59 if m == nil || key == "" {
60 return
61 }
62
63 if m.identityBPS != nil {
64 m.identityBPS.UpdateCopy(func(limits *map[string]int64) {
65 delete(*limits, key)
66 })
67 }
68 m.mu.Lock()
69 delete(m.identityLimiters, key)
70 m.mu.Unlock()
71 }
72
73 func (m *BPSManager) IdentityBPSLimits() map[string]int64 {
74 if m == nil {
75 return nil
76 }
77
78 if m.identityBPS == nil {
79 return nil
80 }
81 return m.identityBPS.Load()
82 }
83
84 func (m *BPSManager) SetIdentityBPSLimits(limits map[string]int64) {
85 if m == nil {
86 return
87 }
88
89 next := make(map[string]int64, len(limits))
90 for key, bps := range limits {
91 if key == "" || bps <= 0 {
92 continue
93 }
94 next[key] = bps
95 }
96
97 if m.identityBPS != nil {
98 m.identityBPS.Store(next)
99 }
100 m.mu.Lock()
101 m.identityLimiters = make(map[string]*bpsLimiter)
102 m.mu.Unlock()
103 }
104
105 func (m *BPSManager) ThrottleIdentityBPS(key string, maxBytes int) int {
106 if m == nil || key == "" || maxBytes <= 0 {
107 return maxBytes
108 }
109
110 for {
111 bps, limiter := m.identityLimiter(key)
112 if bps <= 0 || limiter == nil {
113 return maxBytes
114 }
115 chunkSize := bpsChunkSize(maxBytes, bps)
116 if wait := limiter.reserve(float64(chunkSize), float64(bps)); wait > 0 {
117 time.Sleep(wait)
118 continue
119 }
120 return chunkSize
121 }
122 }
123
124 func (m *BPSManager) identityLimiter(key string) (int64, *bpsLimiter) {
125 bps := m.IdentityBPS(key)
126 if bps <= 0 {
127 return 0, nil
128 }
129
130 m.mu.RLock()
131 limiter := m.identityLimiters[key]
132 m.mu.RUnlock()
133 if limiter != nil {
134 return bps, limiter
135 }
136
137 m.mu.Lock()
138 defer m.mu.Unlock()
139
140 bps = m.IdentityBPS(key)
141 if bps <= 0 {
142 return 0, nil
143 }
144 if m.identityLimiters == nil {
145 m.identityLimiters = make(map[string]*bpsLimiter)
146 }
147 limiter = m.identityLimiters[key]
148 if limiter == nil {
149 limiter = &bpsLimiter{}
150 m.identityLimiters[key] = limiter
151 }
152 return bps, limiter
153 }
154
155 func bpsChunkSize(length int, bps int64) int {
156 if bps <= 0 {
157 return length
158 }
159 chunk := bps / 10
160 if chunk < 1 {
161 chunk = 1
162 }
163 if chunk > int64(length) {
164 chunk = int64(length)
165 }
166 return int(chunk)
167 }
168
169 type bpsLimiter struct {
170 mu sync.Mutex
171 tokens float64
172 updatedAt time.Time
173 }
174
175 func (l *bpsLimiter) reserve(bytes, bps float64) time.Duration {
176 l.mu.Lock()
177 defer l.mu.Unlock()
178
179 now := time.Now()
180 if l.updatedAt.IsZero() {
181 l.updatedAt = now
182 } else if elapsed := now.Sub(l.updatedAt).Seconds(); elapsed > 0 {
183 l.tokens += elapsed * bps
184 l.updatedAt = now
185 }
186 if l.tokens > bps {
187 l.tokens = bps
188 }
189
190 if l.tokens >= bytes {
191 l.tokens -= bytes
192 return 0
193 }
194
195 missing := bytes - l.tokens
196 l.tokens = 0
197 l.updatedAt = now
198 return time.Duration(missing / bps * float64(time.Second))
199 }