feat: multi-node device limit, optimize rate limiter
This commit is contained in:
@@ -43,9 +43,8 @@ func (t *SpeedTracker) SetLogCallback(f SpeedTrackerLogCallback) {
|
||||
t.logFunc = f
|
||||
}
|
||||
|
||||
// UpdateBuckets performs an incremental update of rate limiter buckets.
|
||||
// It reuses existing rate.Limiter instances to avoid burst resets and
|
||||
// minimizes memory allocations.
|
||||
// UpdateBuckets updates the UUID→userID mapping and cleans up removed users.
|
||||
// Rate limiters are created on-demand in GetLimiter.
|
||||
func (t *SpeedTracker) UpdateBuckets() {
|
||||
currentUsers := make([]panel.User, 0, 32)
|
||||
t.limiter.mu.RLock()
|
||||
@@ -66,29 +65,9 @@ func (t *SpeedTracker) UpdateBuckets() {
|
||||
if user.UUID != "" {
|
||||
newUUIDMap[user.UUID] = user.ID
|
||||
}
|
||||
|
||||
if user.SpeedLimit <= 0 {
|
||||
delete(t.buckets, user.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
bytesPerSec := int(user.SpeedLimit) * 1_000_000 / 8
|
||||
burst := bytesPerSec
|
||||
if burst < 64*1024 {
|
||||
burst = 64 * 1024
|
||||
}
|
||||
if cap4s := bytesPerSec * 4; cap4s > 64*1024 && burst > cap4s {
|
||||
burst = cap4s
|
||||
}
|
||||
|
||||
if existing, ok := t.buckets[user.ID]; ok {
|
||||
existing.SetLimit(rate.Limit(bytesPerSec))
|
||||
existing.SetBurst(burst)
|
||||
} else {
|
||||
t.buckets[user.ID] = rate.NewLimiter(rate.Limit(bytesPerSec), burst)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up buckets for removed users
|
||||
for id := range t.buckets {
|
||||
if _, ok := activeIDs[id]; !ok {
|
||||
delete(t.buckets, id)
|
||||
@@ -105,15 +84,53 @@ func (t *SpeedTracker) UpdateBuckets() {
|
||||
}
|
||||
|
||||
// GetLimiter returns the rate limiter for the given user UUID, or nil if
|
||||
// no limit applies. This is called by ConnTracker for every new connection.
|
||||
// no limit applies. Creates limiter on-demand if not exists.
|
||||
// Thread-safe.
|
||||
func (t *SpeedTracker) GetLimiter(user string) *rate.Limiter {
|
||||
if !t.hasLimits.Load() {
|
||||
t.mu.RLock()
|
||||
uid, exists := t.uuidMap[user]
|
||||
if !exists {
|
||||
t.mu.RUnlock()
|
||||
return nil
|
||||
}
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
return t.buckets[t.uuidMap[user]]
|
||||
if lim, ok := t.buckets[uid]; ok {
|
||||
t.mu.RUnlock()
|
||||
return lim
|
||||
}
|
||||
t.mu.RUnlock()
|
||||
|
||||
// Get user info from limiter
|
||||
t.limiter.mu.RLock()
|
||||
u, userExists := t.limiter.users[uid]
|
||||
t.limiter.mu.RUnlock()
|
||||
|
||||
if !userExists || u.SpeedLimit <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create limiter on-demand
|
||||
bytesPerSec := int(u.SpeedLimit) * 1_000_000 / 8
|
||||
burst := bytesPerSec
|
||||
if burst < 64*1024 {
|
||||
burst = 64 * 1024
|
||||
}
|
||||
if cap4s := bytesPerSec * 4; cap4s > 64*1024 && burst > cap4s {
|
||||
burst = cap4s
|
||||
}
|
||||
|
||||
lim := rate.NewLimiter(rate.Limit(bytesPerSec), burst)
|
||||
|
||||
t.mu.Lock()
|
||||
// Double-check after acquiring write lock
|
||||
if existing, ok := t.buckets[uid]; ok {
|
||||
t.mu.Unlock()
|
||||
return existing
|
||||
}
|
||||
t.buckets[uid] = lim
|
||||
t.hasLimits.Store(true)
|
||||
t.mu.Unlock()
|
||||
|
||||
return lim
|
||||
}
|
||||
|
||||
// HasLimits returns true if any user currently has a speed limit configured.
|
||||
|
||||
Reference in New Issue
Block a user