Skip to content

Commit 3e9b135

Browse files
fix(discord): bound voice speaker context cache
1 parent e04ea79 commit 3e9b135

2 files changed

Lines changed: 31 additions & 0 deletions

File tree

extensions/discord/src/voice/speaker-context.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,25 @@ describe("DiscordVoiceSpeakerContextResolver", () => {
5555

5656
expect(fetchMember).toHaveBeenCalledTimes(2);
5757
});
58+
59+
it("evicts the oldest speaker context after the cache reaches its production bound", async () => {
60+
vi.useFakeTimers();
61+
vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
62+
const fetchMember = vi.fn(async (_guildId: string, userId: string) => ({
63+
nickname: userId,
64+
roles: [],
65+
user: { id: userId, username: userId, globalName: userId },
66+
}));
67+
const resolver = new DiscordVoiceSpeakerContextResolver({
68+
client: createClient(fetchMember),
69+
});
70+
71+
for (let index = 0; index <= 5_000; index += 1) {
72+
await resolver.resolveContext("g1", `u${index}`);
73+
}
74+
await resolver.resolveContext("g1", "u0");
75+
await resolver.resolveContext("g1", "u5000");
76+
77+
expect(fetchMember).toHaveBeenCalledTimes(5_002);
78+
});
5879
});

extensions/discord/src/voice/speaker-context.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { resolveDiscordOwnerAccess } from "../monitor/allow-list.js";
88
import { formatDiscordUserTag } from "../monitor/format.js";
99

1010
const SPEAKER_CONTEXT_CACHE_TTL_MS = 60_000;
11+
const SPEAKER_CONTEXT_CACHE_MAX_ENTRIES = 5_000;
1112

1213
type VoiceSpeakerIdentity = {
1314
id: string;
@@ -136,6 +137,15 @@ export class DiscordVoiceSpeakerContextResolver {
136137
...context,
137138
expiresAt,
138139
});
140+
// Expiry is checked on lookup, so one-time speakers would otherwise remain
141+
// for the full voice-manager lifetime even after their entries expire.
142+
while (this.cache.size > SPEAKER_CONTEXT_CACHE_MAX_ENTRIES) {
143+
const oldestKey = this.cache.keys().next().value;
144+
if (oldestKey === undefined) {
145+
break;
146+
}
147+
this.cache.delete(oldestKey);
148+
}
139149
}
140150
}
141151
}

0 commit comments

Comments
 (0)