fix(discord): prevent voice speaker cache growth#110907
fix(discord): prevent voice speaker cache growth#110907zhangguiping-xydt wants to merge 1 commit into
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed July 18, 2026, 3:54 PM ET / 19:54 UTC. Summary PR surface: Source +10, Tests +21. Total +31 across 2 files. Reproducibility: yes. at source level: applying the supplied focused test to current main drives 5,001 unique keys and shows that TTL-only lookup cleanup leaves the oldest key cached. This review did not execute contributor code, so the status remains source-reproducible rather than reproduced here. Review metrics: none identified. Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Next step before merge
Security Review detailsBest possible solution: Keep the owner-local 5,000-entry insertion-order bound and merge only after a redacted real Discord voice-runtime proof demonstrates the changed cache behavior without exposing private guild, user, endpoint, or credential data. Do we have a high-confidence way to reproduce the issue? Yes, at source level: applying the supplied focused test to current main drives 5,001 unique keys and shows that TTL-only lookup cleanup leaves the oldest key cached. This review did not execute contributor code, so the status remains source-reproducible rather than reproduced here. Is this the best way to solve the issue? Yes. A fixed insertion-order cap in the resolver that owns the transient Map is narrower than adding a lifecycle timer or mixing speaker identity state with another Discord cache, and it preserves the existing TTL and public surfaces. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 1d492f5c5378. Label changesLabel justifications:
Evidence reviewedPR surface: Source +10, Tests +21. Total +31 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (1 earlier review cycle)
|
|
Closing because the remaining review gate requires a real Discord voice runtime transcript that exercises the speaker-context path. This environment has neither the shared Discord QA credentials nor permission to dispatch the upstream credentialed QA workflow, and the existing auto-join scenario alone would not exercise the bounded cache. The source-level eviction test is valid, but I cannot honestly supply the requested external-boundary proof. I am replacing this with a separate Discord Gateway queue-bound fix that can be demonstrated through the actual production GatewayPlugin over a real loopback WebSocket connection. |
|
Replacement submitted as #110954. It remains Discord-specific and bounds a long-lived queue, but its failure boundary is below the external Discord service: the PR includes before/after proof through the production GatewayPlugin over a real loopback WebSocket connection. |
What Problem This Solves
Fixes an issue where long-running Discord voice accounts could retain an unbounded number of expired speaker-context entries after encountering many unique speakers.
The cache uses a 60-second TTL, but expiry was only checked when the same guild/user key was looked up again. One-time speakers therefore remained in the voice manager's cache for its entire process lifetime.
Why This Change Was Made
The Discord voice speaker-context owner now caps the cache at 5,000 entries and evicts the oldest inserted entry when the cap is exceeded. This matches the fixed-TTL ordering: the oldest entry is also the first one due to expire.
The change does not alter the existing 60-second TTL, Discord REST behavior, permission checks, configuration, or public Plugin SDK surfaces.
User Impact
Discord voice bots that run for long periods or span many guilds no longer accumulate stale speaker identity contexts without a memory bound. Active and recently observed speakers continue to use the existing cache behavior.
Evidence
Exact commit:
3e9b135b2f619343c619a56b2ca31f4e644bd53cFinal pre-push checks:
origin/main:5fdb0cd24e68cd27c54ec32c821e8d0165b2e7acextensions/discord/src/voice/speaker-context.tsor its test (all current open-PR pages checked)speaker context cache,discord voice cache bound, andone-time speakers cachereturned no matchesBefore: regression proof
The regression drives the public production
resolveContext()method through 5,001 unique Discord speaker keys, then looks up the oldest key again. On unpatched main, the oldest entry is still cached, so the client lookup count remains 5,001 instead of reaching 5,002.After: focused regression
The final test also re-reads the newest key after the overflow, proving the oldest entry is evicted while a recent entry remains cached.
Adjacent Discord voice coverage
Formatting and lint
The local
check:changedfallback passed conflict-marker, max-lines, attribution, wildcard-export, duplicate-coverage, dependency-pin, formatting, Plugin SDK API, deprecated-API, plugin-boundary, package-patch, and temp-creation checks. It then stopped in the repository-wide extension typecheck on existingpackages/net-policy/src/ip.tsdependency/type mismatches outside this two-file diff. Blacksmith was unavailable locally and direct AWS Crabbox was not authenticated; the PR's exact-head CI is therefore the authoritative typecheck/lint fan-out.Review
The repository autoreview helper ran with Codex
gpt-5.6-solfor approximately 17 minutes, then exited at the model-engine layer withcodex engine failed (1)before returning findings or a clean verdict. It did not report a code finding. An equivalent manual review covered the changed class, the long-livedDiscordVoiceManagerowner, voice ingress and participant-roster callers, the Discord entity/thread/presence cache siblings, concurrency behavior, TTL ordering, and adjacent tests.Best-fix conclusion: keeping the bound inside
DiscordVoiceSpeakerContextResolveris the narrow owner-boundary fix. A timer/sweep would add lifecycle work, and reusing the REST entity cache would mix different value and invalidation contracts.