perf(main): refresh message feed cache in background

This commit is contained in:
777genius 2026-05-31 12:44:39 +03:00
parent 6bb8d87bc7
commit 9c29b0f8f1
2 changed files with 61 additions and 4 deletions

View file

@ -449,6 +449,14 @@ export class TeamMessageFeedService {
messages: cached.messages,
};
}
if (cached && !cacheDirty && cacheExpired) {
this.refreshCleanExpiredCacheInBackground(teamName, cached, now);
return {
teamName,
feedRevision: cached.feedRevision,
messages: cached.messages,
};
}
const existingRequest = this.inFlightByTeam.get(teamName);
const generationAtStart = this.getGeneration(teamName);
@ -479,6 +487,43 @@ export class TeamMessageFeedService {
return this.generationByTeam.get(teamName) ?? 0;
}
private refreshCleanExpiredCacheInBackground(
teamName: string,
cached: TeamMessageFeedCacheEntry,
now: number
): void {
const generationAtStart = this.getGeneration(teamName);
const existingRequest = this.inFlightByTeam.get(teamName);
if (existingRequest?.generationAtStart === generationAtStart) {
return;
}
const request = this.buildFeed(teamName, cached, now, false, true, generationAtStart).catch(
(error) => {
logger.debug(
`[${teamName}] background message feed refresh failed: ${
error instanceof Error ? error.message : String(error)
}`
);
return {
teamName,
feedRevision: cached.feedRevision,
messages: cached.messages,
};
}
);
const trackedRequest = request.finally(() => {
if (this.inFlightByTeam.get(teamName)?.promise === trackedRequest) {
this.inFlightByTeam.delete(teamName);
}
});
this.inFlightByTeam.set(teamName, {
promise: trackedRequest,
generationAtStart,
});
}
private async buildFeed(
teamName: string,
cached: TeamMessageFeedCacheEntry | undefined,

View file

@ -167,9 +167,12 @@ Messages:
]);
});
it('refreshes the durable feed after cache expiry even when the dirty signal was missed', async () => {
let inboxMessages: InboxMessage[] = [makeMessage()];
const getInboxMessages = vi.fn(async () => inboxMessages);
it('returns clean expired cache immediately and refreshes durable feed in the background', async () => {
const refreshRequest = createDeferred<InboxMessage[]>();
const getInboxMessages = vi
.fn()
.mockResolvedValueOnce([makeMessage()])
.mockImplementationOnce(() => refreshRequest.promise);
const service = new TeamMessageFeedService({
getConfig: vi.fn(async () => config),
getInboxMessages,
@ -179,7 +182,7 @@ Messages:
await service.getFeed('signal-ops-4');
inboxMessages = [
const refreshedMessages = [
makeMessage({
from: 'jack',
to: 'user',
@ -192,6 +195,15 @@ Messages:
vi.setSystemTime(new Date('2026-04-19T18:46:46.500Z'));
const stale = await service.getFeed('signal-ops-4');
expect(getInboxMessages).toHaveBeenCalledTimes(2);
expect(stale.messages).toHaveLength(1);
refreshRequest.resolve(refreshedMessages);
await refreshRequest.promise;
await Promise.resolve();
await Promise.resolve();
const refreshed = await service.getFeed('signal-ops-4');
expect(getInboxMessages).toHaveBeenCalledTimes(2);
expect(