mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(line): prevent profile cache growth across users (#109750)
* fix(line): prevent profile cache growth across users * docs: explain LINE profile cache eviction order Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com> --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
co-authored by
Peter Steinberger
parent
d023e8b412
commit
72f4cbd8f2
@@ -515,6 +515,19 @@ describe("LINE send helpers", () => {
|
||||
expect(getProfileMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("bounds profile cache entries across distinct users", async () => {
|
||||
getProfileMock.mockImplementation(async (userId: string) => ({
|
||||
displayName: userId,
|
||||
}));
|
||||
|
||||
for (let index = 0; index <= 1000; index += 1) {
|
||||
await sendModule.getUserProfile(`U-profile-${index}`, { cfg: LINE_TEST_CFG });
|
||||
}
|
||||
await sendModule.getUserProfile("U-profile-0", { cfg: LINE_TEST_CFG });
|
||||
|
||||
expect(getProfileMock).toHaveBeenCalledTimes(1002);
|
||||
});
|
||||
|
||||
it("continues when loading animation is unsupported", async () => {
|
||||
showLoadingAnimationMock.mockRejectedValueOnce(new Error("unsupported"));
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Line plugin module implements send behavior.
|
||||
import { messagingApi } from "@line/bot-sdk";
|
||||
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
||||
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
@@ -29,6 +30,25 @@ const userProfileCache = new Map<
|
||||
{ displayName: string; pictureUrl?: string; fetchedAt: number }
|
||||
>();
|
||||
const PROFILE_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
const PROFILE_CACHE_MAX_ENTRIES = 1000;
|
||||
|
||||
function cacheUserProfile(
|
||||
userId: string,
|
||||
profile: { displayName: string; pictureUrl?: string; fetchedAt: number },
|
||||
): void {
|
||||
// Refresh insertion order so overflow evicts expired entries first, then the oldest live fetch.
|
||||
userProfileCache.delete(userId);
|
||||
userProfileCache.set(userId, profile);
|
||||
if (userProfileCache.size <= PROFILE_CACHE_MAX_ENTRIES) {
|
||||
return;
|
||||
}
|
||||
for (const [key, cached] of userProfileCache) {
|
||||
if (profile.fetchedAt - cached.fetchedAt >= PROFILE_CACHE_TTL_MS) {
|
||||
userProfileCache.delete(key);
|
||||
}
|
||||
}
|
||||
pruneMapToMaxSize(userProfileCache, PROFILE_CACHE_MAX_ENTRIES);
|
||||
}
|
||||
|
||||
interface LineSendOpts {
|
||||
cfg: OpenClawConfig;
|
||||
@@ -511,7 +531,7 @@ export async function getUserProfile(
|
||||
pictureUrl: profile.pictureUrl,
|
||||
};
|
||||
|
||||
userProfileCache.set(userId, {
|
||||
cacheUserProfile(userId, {
|
||||
...result,
|
||||
fetchedAt: Date.now(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user