8000 fix(gateway): fix identify rate limiter deadlock and spurious unlock on close by dearlyzin · Pull Request #657 · disgoorg/disgo · GitHub
[go: up one dir, main page]

Skip to content

fix(gateway): fix identify rate limiter deadlock and spurious unlock on close - #657

Closed
dearlyzin wants to merge 1 commit into
disgoorg:masterfrom
dearlyzin:fix/identify-rate-limiter-close-deadlock
Closed

dearlyzin wants to merge 1 commit into
disgoorg:masterfrom
dearlyzin:fix/identify-rate-limiter-close-deadlock

Conversation

@dearlyzin
Copy link
Copy Markdown

Problem

identifyRateLimiterImpl.Close leaves r.mu locked forever after returning. Any subsequent Wait or Unlock call (both go through getBucket which locks r.mu) deadlocks permanently.

When sharding, the identify rate limiter is shared across all shards of the shard manager, so closing a single gateway (e.g. on graceful shutdown) blocks identify attempts of every remaining shard forever.

Additionally, the goroutines spawned by Close called b.mu.Unlock() unconditionally, even when b.mu.CLock(ctx) failed (e.g. context expired while a bucket was held by an in-flight identify). Unlocking a mutex without owning it either silently releases a lock owned by another goroutine (letting a second shard identify concurrently, violating max concurrency) or panics with unlock of unlocked mutex (csync panics when the counter goes negative, which happens when CLock returned early without acquiring).

Root cause

  • gateway/identify_rate_limiter.go:58: r.mu.Lock() inside Close with no matching unlock anywhere on the function's paths.
  • gateway/identify_rate_limiter.go:68: b.mu.Unlock() executed even when CLock returned an error, contradicting the interface contract documented at gateway/identify_rate_limiter.go:26 ("If Wait fails, Unlock should not be called").

Changes

  • Release r.mu via defer after locking it in Close, keeping mutual exclusion while buckets are being closed but making the limiter usable afterwards.
  • Only unlock a bucket when its CLock succeeded; on failure the goroutine logs and returns without touching the mutex.

Verification

  • Added deterministic regression tests in gateway/identify_rate_limiter_close_test.go:
    • TestIdentifyRateLimiterImpl_CloseThenWait: fails on master with "Wait deadlocked after Close: r.mu left locked", passes after the fix.
    • TestIdentifyRateLimiterImpl_CloseKeepsLockedBucketLocked: ensures a bucket held by an in-flight identify is not spuriously released when Close fails to acquire it.
  • go build ./... passes.
  • go vet ./gateway passes.
  • go test ./gateway/ passes, including with -race.

@topi314 topi314 closed this Nov 11, 2026
@dearlyzin
dearlyzin deleted the fix/identify-rate-limiter-close-deadlock branch November 11, 2026 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants

0