mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
- upload_handler: uses PermissionService.HasChannelPerm (removes last hasChannelPermREST usage) - profile_handler: delegates to UserService with proper ErrConflict on duplicate username - topic_rate_limiter: per-topic throughput caps (100 msg/s default), wired into deliverBroadcast for channel-scoped broadcasts - voice_join: subscribes client to VoiceTopic on join - voice_leave: unsubscribes client from VoiceTopic on leave https://claude.ai/code/session_01CBFF3r84ywkJRWwuqw8zD8
72 lines
1.7 KiB
Go
72 lines
1.7 KiB
Go
package ws
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// TopicRateLimiter enforces per-topic throughput caps to prevent a single
|
|
// busy channel from saturating the broadcast loop and starving others.
|
|
type TopicRateLimiter struct {
|
|
mu sync.Mutex
|
|
buckets map[Topic]*tokenBucket
|
|
defaultRate int // messages per window
|
|
window time.Duration // window size
|
|
}
|
|
|
|
// tokenBucket is a simple sliding-window rate limiter per topic.
|
|
type tokenBucket struct {
|
|
tokens int
|
|
lastReset time.Time
|
|
}
|
|
|
|
// NewTopicRateLimiter creates a rate limiter with the given default rate.
|
|
// rate is the maximum messages per window for any single topic.
|
|
func NewTopicRateLimiter(rate int, window time.Duration) *TopicRateLimiter {
|
|
return &TopicRateLimiter{
|
|
buckets: make(map[Topic]*tokenBucket),
|
|
defaultRate: rate,
|
|
window: window,
|
|
}
|
|
}
|
|
|
|
// Allow checks whether a message to the given topic is within the rate limit.
|
|
// Returns true if allowed, false if the topic has exceeded its quota.
|
|
func (trl *TopicRateLimiter) Allow(topic Topic) bool {
|
|
trl.mu.Lock()
|
|
defer trl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
b, ok := trl.buckets[topic]
|
|
if !ok {
|
|
b = &tokenBucket{tokens: trl.defaultRate, lastReset: now}
|
|
trl.buckets[topic] = b
|
|
}
|
|
|
|
// Reset bucket if window has elapsed.
|
|
if now.Sub(b.lastReset) >= trl.window {
|
|
b.tokens = trl.defaultRate
|
|
b.lastReset = now
|
|
}
|
|
|
|
if b.tokens <= 0 {
|
|
return false
|
|
}
|
|
b.tokens--
|
|
return true
|
|
}
|
|
|
|
// Cleanup removes stale buckets that haven't been used recently.
|
|
// Call periodically to prevent unbounded memory growth.
|
|
func (trl *TopicRateLimiter) Cleanup(maxAge time.Duration) {
|
|
trl.mu.Lock()
|
|
defer trl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
for topic, b := range trl.buckets {
|
|
if now.Sub(b.lastReset) > maxAge {
|
|
delete(trl.buckets, topic)
|
|
}
|
|
}
|
|
}
|