(new): expand runtime APIs
(fix): harden concurrent lifecycle (tests): add regression coverage (doc): update v1.2 guidance
This commit is contained in:
+28
-4
@@ -28,7 +28,8 @@ type RateLimiter struct {
|
||||
chatLocks map[int64]time.Time // per-chat cooldown timestamps
|
||||
chatLimiters map[int64]*rate.Limiter // per-chat token buckets (1 req/sec)
|
||||
chatLastSeen map[int64]time.Time // last access timestamp per chat, for Cleanup eviction
|
||||
chatMu sync.RWMutex // protects chatLocks, chatLimiters, and chatLastSeen
|
||||
chatActive map[int64]int // in-flight users of each per-chat limiter
|
||||
chatMu sync.RWMutex // protects all per-chat maps
|
||||
}
|
||||
|
||||
// NewRateLimiter creates a new RateLimiter with default limits.
|
||||
@@ -40,6 +41,7 @@ func NewRateLimiter() *RateLimiter {
|
||||
chatLimiters: make(map[int64]*rate.Limiter),
|
||||
chatLocks: make(map[int64]time.Time),
|
||||
chatLastSeen: make(map[int64]time.Time),
|
||||
chatActive: make(map[int64]int),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,11 +60,12 @@ func (rl *RateLimiter) Cleanup(idleThreshold time.Duration) {
|
||||
defer rl.chatMu.Unlock()
|
||||
|
||||
for chatID, lastSeen := range rl.chatLastSeen {
|
||||
if now.Sub(lastSeen) <= idleThreshold {
|
||||
if now.Sub(lastSeen) <= idleThreshold || rl.chatActive[chatID] > 0 {
|
||||
continue
|
||||
}
|
||||
delete(rl.chatLimiters, chatID)
|
||||
delete(rl.chatLastSeen, chatID)
|
||||
delete(rl.chatActive, chatID)
|
||||
}
|
||||
for chatID, until := range rl.chatLocks {
|
||||
if !until.After(now) {
|
||||
@@ -131,7 +134,8 @@ func (rl *RateLimiter) Wait(ctx context.Context, chatID int64) error {
|
||||
if err := rl.waitForChatUnlock(ctx, chatID); err != nil {
|
||||
return err
|
||||
}
|
||||
chatLimiter := rl.getChatLimiter(chatID)
|
||||
chatLimiter, release := rl.acquireChatLimiter(chatID)
|
||||
defer release()
|
||||
if err := chatLimiter.Wait(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -200,7 +204,8 @@ func (rl *RateLimiter) Allow(chatID int64) bool {
|
||||
}
|
||||
}
|
||||
|
||||
chatLimiter := rl.getChatLimiter(chatID)
|
||||
chatLimiter, release := rl.acquireChatLimiter(chatID)
|
||||
defer release()
|
||||
chatReservation := chatLimiter.ReserveN(now, 1)
|
||||
if !chatReservation.OK() || chatReservation.DelayFrom(now) > 0 {
|
||||
chatReservation.CancelAt(now)
|
||||
@@ -311,3 +316,22 @@ func (rl *RateLimiter) getChatLimiter(chatID int64) *rate.Limiter {
|
||||
rl.chatLastSeen[chatID] = now
|
||||
return lim
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) acquireChatLimiter(chatID int64) (*rate.Limiter, func()) {
|
||||
rl.chatMu.Lock()
|
||||
limiter, ok := rl.chatLimiters[chatID]
|
||||
if !ok {
|
||||
limiter = rate.NewLimiter(1, 1)
|
||||
rl.chatLimiters[chatID] = limiter
|
||||
}
|
||||
rl.chatLastSeen[chatID] = time.Now()
|
||||
rl.chatActive[chatID]++
|
||||
rl.chatMu.Unlock()
|
||||
|
||||
return limiter, func() {
|
||||
rl.chatMu.Lock()
|
||||
rl.chatActive[chatID]--
|
||||
rl.chatLastSeen[chatID] = time.Now()
|
||||
rl.chatMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user