(new): expand runtime APIs
Golang lint / lint (push) Successful in 58s
Golang lint / lint (pull_request) Successful in 13m10s

(fix): harden concurrent lifecycle
(tests): add regression coverage
(doc): update v1.2 guidance
This commit is contained in:
2026-08-20 11:08:45 +03:00
parent 29b208eeec
commit 24040fe164
37 changed files with 1129 additions and 157 deletions
+28 -4
View File
@@ -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()
}
}