From 6116c6411e2f84b7eab5146c68e8aa144f962b25 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Tue, 5 May 2026 13:34:45 -0500 Subject: [PATCH] fix sliding sync recursion by avoiding notif fixup reentry --- .../fix-sliding-sync-call-stack-crash.md | 5 +++ src/app/state/room/roomToUnread.ts | 3 +- src/app/utils/room.ts | 12 +++++-- src/client/initMatrix.ts | 33 ++++++++++++------- 4 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 .changeset/fix-sliding-sync-call-stack-crash.md diff --git a/.changeset/fix-sliding-sync-call-stack-crash.md b/.changeset/fix-sliding-sync-call-stack-crash.md new file mode 100644 index 000000000..2ca6fdc8d --- /dev/null +++ b/.changeset/fix-sliding-sync-call-stack-crash.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix the call stack size crash on load when sliding sync is enabled. diff --git a/src/app/state/room/roomToUnread.ts b/src/app/state/room/roomToUnread.ts index 208df289c..24ded9f80 100644 --- a/src/app/state/room/roomToUnread.ts +++ b/src/app/state/room/roomToUnread.ts @@ -312,7 +312,8 @@ export const useBindRoomToUnreadAtom = (mx: MatrixClient, unreadAtom: typeof roo if (room.getMyMembership() !== (KnownMembership.Join as string)) return; const unreadInfo = getUnreadInfo(room, { - applyFixup: shouldApplyUnreadFixup(), + // Counts are already updated before this event would recurse if true + applyFixup: false, mDirects, }); if (unreadInfo.total === 0 && unreadInfo.highlight === 0) { diff --git a/src/app/utils/room.ts b/src/app/utils/room.ts index 2d68b5d86..aa7ea1487 100644 --- a/src/app/utils/room.ts +++ b/src/app/utils/room.ts @@ -287,11 +287,17 @@ type UnreadInfoOptions = { mDirects?: Set; }; +const unreadInfoFixupInProgress = new WeakSet(); + export const getUnreadInfo = (room: Room, options?: UnreadInfoOptions): UnreadInfo => { const userId = room.client.getUserId(); - if (userId && options?.applyFixup) { - // Reconcile known notification-count drift (notably with Sliding Sync / mixed receipts). - room.fixupNotifications(userId); + if (userId && options?.applyFixup && !unreadInfoFixupInProgress.has(room)) { + unreadInfoFixupInProgress.add(room); + try { + room.fixupNotifications(userId); + } finally { + unreadInfoFixupInProgress.delete(room); + } } let total = room.getUnreadNotificationCount(NotificationCountType.Total); diff --git a/src/client/initMatrix.ts b/src/client/initMatrix.ts index 1e6ca9c84..9e0496ee3 100644 --- a/src/client/initMatrix.ts +++ b/src/client/initMatrix.ts @@ -56,11 +56,17 @@ type MatrixClientWithWritableFetchRoomEvent = MatrixClient & { fetchRoomEvent: (roomId: string, eventId: string) => Promise; }; -// Replace fetchRoomEvent for first sync so thread roots don't each trigger GET /event -// Uses cached timeline events when present otherwise a stub that fetches when user opens thread -function installStartupFetchRoomEventPatch(mx: MatrixClient): void { +type StartupFetchRoomEventPatchOptions = { + stubOnCacheMiss: boolean; +}; + +function installStartupFetchRoomEventPatch( + mx: MatrixClient, + options: StartupFetchRoomEventPatchOptions +): void { fetchRoomEventStartupCleanupByClient.get(mx)?.(); + const { stubOnCacheMiss } = options; const mxWritable = mx as MatrixClientWithWritableFetchRoomEvent; const origFetchRoomEvent = mx.fetchRoomEvent.bind(mx); let restored = false; @@ -84,12 +90,17 @@ function installStartupFetchRoomEventPatch(mx: MatrixClient): void { mxWritable.fetchRoomEvent = (roomId: string, eventId: string) => { if (restored) return origFetchRoomEvent(roomId, eventId); const cachedEvent = mx.getRoom(roomId)?.findEventById(eventId); - // Reuse sync payload instead of another GET when we already have the root. - const payload: FetchRoomEventResult = cachedEvent?.event ?? { - event_id: eventId, - room_id: roomId, - }; - return Promise.resolve(payload); + if (cachedEvent) { + return Promise.resolve(cachedEvent.event); + } + if (stubOnCacheMiss) { + const payload: FetchRoomEventResult = { + event_id: eventId, + room_id: roomId, + }; + return Promise.resolve(payload); + } + return origFetchRoomEvent(roomId, eventId); }; mx.on(ClientEvent.Sync, onSync); @@ -518,7 +529,7 @@ export const startClient = async (mx: MatrixClient, config?: StartClientConfig): (filterDefinition.room.timeline as { lazy_load_members?: boolean }).lazy_load_members = true; } - installStartupFetchRoomEventPatch(mx); + installStartupFetchRoomEventPatch(mx, { stubOnCacheMiss: true }); let syncStarted: Promise; try { @@ -693,7 +704,7 @@ export const startClient = async (mx: MatrixClient, config?: StartClientConfig): }); try { - installStartupFetchRoomEventPatch(mx); + installStartupFetchRoomEventPatch(mx, { stubOnCacheMiss: false }); await mx.startClient({ lazyLoadMembers: true, slidingSync: manager.slidingSync,