mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-22 07:20:59 +00:00
Matrix: bound room metadata caches
This commit is contained in:
@@ -152,4 +152,23 @@ describe("createDirectRoomTracker", () => {
|
||||
}),
|
||||
).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("bounds joined-room membership cache size", async () => {
|
||||
const client = createMockClient({ isDm: false });
|
||||
const tracker = createDirectRoomTracker(client);
|
||||
|
||||
for (let i = 0; i <= 1024; i += 1) {
|
||||
await tracker.isDirectMessage({
|
||||
roomId: `!room-${i}:example.org`,
|
||||
senderId: "@alice:example.org",
|
||||
});
|
||||
}
|
||||
|
||||
await tracker.isDirectMessage({
|
||||
roomId: "!room-0:example.org",
|
||||
senderId: "@alice:example.org",
|
||||
});
|
||||
|
||||
expect(client.getJoinedRoomMembers).toHaveBeenCalledTimes(1026);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,17 @@ type DirectRoomTrackerOptions = {
|
||||
};
|
||||
|
||||
const DM_CACHE_TTL_MS = 30_000;
|
||||
const MAX_TRACKED_DM_ROOMS = 1024;
|
||||
|
||||
function rememberBounded<T>(map: Map<string, T>, key: string, value: T): void {
|
||||
map.set(key, value);
|
||||
if (map.size > MAX_TRACKED_DM_ROOMS) {
|
||||
const oldest = map.keys().next().value;
|
||||
if (typeof oldest === "string") {
|
||||
map.delete(oldest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createDirectRoomTracker(client: MatrixClient, opts: DirectRoomTrackerOptions = {}) {
|
||||
const log = opts.log ?? (() => {});
|
||||
@@ -55,7 +66,7 @@ export function createDirectRoomTracker(client: MatrixClient, opts: DirectRoomTr
|
||||
.filter((entry): entry is string => typeof entry === "string")
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean);
|
||||
joinedMembersCache.set(roomId, { members: normalized, ts: now });
|
||||
rememberBounded(joinedMembersCache, roomId, { members: normalized, ts: now });
|
||||
return normalized;
|
||||
} catch (err) {
|
||||
log(`matrix: dm member lookup failed room=${roomId} (${String(err)})`);
|
||||
|
||||
62
extensions/matrix/src/matrix/monitor/room-info.test.ts
Normal file
62
extensions/matrix/src/matrix/monitor/room-info.test.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { MatrixClient } from "../sdk.js";
|
||||
import { createMatrixRoomInfoResolver } from "./room-info.js";
|
||||
|
||||
function createClientStub() {
|
||||
return {
|
||||
getRoomStateEvent: vi.fn(
|
||||
async (
|
||||
roomId: string,
|
||||
eventType: string,
|
||||
stateKey: string,
|
||||
): Promise<Record<string, unknown>> => {
|
||||
if (eventType === "m.room.name") {
|
||||
return { name: `Room ${roomId}` };
|
||||
}
|
||||
if (eventType === "m.room.canonical_alias") {
|
||||
return {
|
||||
alias: `#alias-${roomId}:example.org`,
|
||||
alt_aliases: [`#alt-${roomId}:example.org`],
|
||||
};
|
||||
}
|
||||
if (eventType === "m.room.member") {
|
||||
return { displayname: `Display ${roomId}:${stateKey}` };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
),
|
||||
} as unknown as MatrixClient & {
|
||||
getRoomStateEvent: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
}
|
||||
|
||||
describe("createMatrixRoomInfoResolver", () => {
|
||||
it("caches room info and member display names", async () => {
|
||||
const client = createClientStub();
|
||||
const resolver = createMatrixRoomInfoResolver(client);
|
||||
|
||||
await resolver.getRoomInfo("!room:example.org");
|
||||
await resolver.getRoomInfo("!room:example.org");
|
||||
await resolver.getMemberDisplayName("!room:example.org", "@alice:example.org");
|
||||
await resolver.getMemberDisplayName("!room:example.org", "@alice:example.org");
|
||||
|
||||
expect(client.getRoomStateEvent).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("bounds cached room and member entries", async () => {
|
||||
const client = createClientStub();
|
||||
const resolver = createMatrixRoomInfoResolver(client);
|
||||
|
||||
for (let i = 0; i <= 1024; i += 1) {
|
||||
await resolver.getRoomInfo(`!room-${i}:example.org`);
|
||||
}
|
||||
await resolver.getRoomInfo("!room-0:example.org");
|
||||
|
||||
for (let i = 0; i <= 4096; i += 1) {
|
||||
await resolver.getMemberDisplayName("!room:example.org", `@user-${i}:example.org`);
|
||||
}
|
||||
await resolver.getMemberDisplayName("!room:example.org", "@user-0:example.org");
|
||||
|
||||
expect(client.getRoomStateEvent).toHaveBeenCalledTimes(6150);
|
||||
});
|
||||
});
|
||||
@@ -6,8 +6,22 @@ export type MatrixRoomInfo = {
|
||||
altAliases: string[];
|
||||
};
|
||||
|
||||
const MAX_TRACKED_ROOM_INFO = 1024;
|
||||
const MAX_TRACKED_MEMBER_DISPLAY_NAMES = 4096;
|
||||
|
||||
function rememberBounded<T>(map: Map<string, T>, key: string, value: T, maxEntries: number): void {
|
||||
map.set(key, value);
|
||||
if (map.size > maxEntries) {
|
||||
const oldest = map.keys().next().value;
|
||||
if (typeof oldest === "string") {
|
||||
map.delete(oldest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createMatrixRoomInfoResolver(client: MatrixClient) {
|
||||
const roomInfoCache = new Map<string, MatrixRoomInfo>();
|
||||
const memberDisplayNameCache = new Map<string, string>();
|
||||
|
||||
const getRoomInfo = async (roomId: string): Promise<MatrixRoomInfo> => {
|
||||
const cached = roomInfoCache.get(roomId);
|
||||
@@ -40,16 +54,27 @@ export function createMatrixRoomInfoResolver(client: MatrixClient) {
|
||||
// ignore
|
||||
}
|
||||
const info = { name, canonicalAlias, altAliases };
|
||||
roomInfoCache.set(roomId, info);
|
||||
rememberBounded(roomInfoCache, roomId, info, MAX_TRACKED_ROOM_INFO);
|
||||
return info;
|
||||
};
|
||||
|
||||
const getMemberDisplayName = async (roomId: string, userId: string): Promise<string> => {
|
||||
const cacheKey = `${roomId}:${userId}`;
|
||||
const cached = memberDisplayNameCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
const memberState = await client
|
||||
.getRoomStateEvent(roomId, "m.room.member", userId)
|
||||
.catch(() => null);
|
||||
if (memberState && typeof memberState.displayname === "string") {
|
||||
rememberBounded(
|
||||
memberDisplayNameCache,
|
||||
cacheKey,
|
||||
memberState.displayname,
|
||||
MAX_TRACKED_MEMBER_DISPLAY_NAMES,
|
||||
);
|
||||
return memberState.displayname;
|
||||
}
|
||||
return userId;
|
||||
|
||||
Reference in New Issue
Block a user