Skip to content

fix: tighten sliding sync memory management#348

Merged
dozro merged 10 commits intodevfrom
fix/sliding-sync-memory
Mar 19, 2026
Merged

fix: tighten sliding sync memory management#348
dozro merged 10 commits intodevfrom
fix/sliding-sync-memory

Conversation

@Just-Insane
Copy link
Copy Markdown
Contributor

@Just-Insane Just-Insane commented Mar 17, 2026

Summary

A series of improvements to SlidingSyncManager memory handling, worked through in four steps.

1. Stop polling loop on dispose()

SlidingSyncManager.dispose() removed event listeners but never called this.slidingSync.stop(). The SDK's internal HTTP polling loop kept running indefinitely after the client was "disposed", leaking network connections and blocking garbage collection.

Fix: dispose() now calls this.slidingSync.stop() before removing listeners.

2. Remove adaptive timeline-limit logic

SlidingSyncManager previously adjusted per-room timeline limits dynamically based on connection quality (ACTIVE_ROOM_TIMELINE_LIMIT_LOW / HIGH). This complexity caused problems (spurious scroll resets on bulk-loads) and the adaptive behaviour wasn't observable in practice. All adaptive constants, helpers, and connection-quality listeners have been removed. The active room subscription now always requests 50 events.

3. Stop unsubscribing on navigation

useSlidingSyncActiveRoom previously called unsubscribeFromRoom() in its cleanup function, so every room visited would have its subscription removed even if the user briefly navigated away and came back. The hook now only switches the active-room subscription; subscriptions accumulate across the session so returning to a room is instant.

4. Auto-unsubscribe on leave / ban

onMembershipLeave handler registered on RoomMemberEvent.Membership in attach() / dispose(). When the local user leaves or is banned from a currently-subscribed room, unsubscribeFromRoom() fires automatically to clean up the sliding sync subscription.

Additional Information

These changes are all loosely related to each other, and all work towards the goal of making slding sync more in-line with how it is handled in Element Web.

There are some things to note:

  • Since we never remove rooms from the list, if the user joins a bunch of active rooms and then leaves the app open, it could use a decent amount of memory (50-100 MB), however, the SDK will cap timelines at 1000 entries, so it shouldn't be too bad.
  • This significantly reduces the number of re-renders that were occuring, previously every time you entered a room sliding sync would need to add a room connection and then re-render. Now, since the room is connected and not unsubscribed, room content loads gracefully without a full re-render of the timeline.
  • The Adaptive Timeline Limit has been removed, as it was not really noticeable in practice and added significantly more complexity to the design for little/no gain.
  • If you leave a room, we kill the connection now, seems logical.

Tests

6 unit tests in slidingSync.test.ts:

  • dispose() calls stop() on the sliding sync instance
  • Membership leave: unsubscribes on leave
  • Membership leave: unsubscribes on ban
  • Membership leave: ignores other-user membership changes
  • Membership leave: ignores join
  • Membership leave: no-op for rooms that were never subscribed

Two memory management issues fixed:

1. SlidingSyncManager.dispose() now calls this.slidingSync.stop() before
   removing listeners. Without this the SDK's internal Promise loop keeps
   polling the sliding sync proxy even after the client is disposed, leaking
   network connections and preventing garbage collection.

2. pruneRoomTimeline() is called from unsubscribeFromRoom(). When a room
   leaves the active subscription its EventTimeline chain can hold hundreds of
   events in memory. Once the event count exceeds PRUNE_TIMELINE_THRESHOLD
   (= ACTIVE_ROOM_TIMELINE_LIMIT_HIGH = 150) the live timeline is reset via
   EventTimelineSet.resetLiveTimeline(). The full history remains on disk in
   IndexedDBStore and is re-fetched via the active-room subscription on next
   open, so there is no user-visible change.

Adds 5 unit tests: 1 for dispose, 4 for the pruning threshold boundary.
@Just-Insane Just-Insane requested review from 7w1 and hazre as code owners March 17, 2026 21:58
@Just-Insane Just-Insane marked this pull request as draft March 17, 2026 22:16
@Just-Insane Just-Insane reopened this Mar 18, 2026
…gation

Replace the adaptive 50/100/150 timeline limit system with a flat
ACTIVE_ROOM_TIMELINE_LIMIT=50, matching Element Web approach.
Stop calling unsubscribeFromRoom() on room navigation so subscriptions
accumulate across the session for instant room switching.

Element Web SlidingSyncManager.setRoomVisible() never removes subscriptions
at all -- no pruning whatsoever. Our PRUNE_TIMELINE_THRESHOLD=100 guard
is more conservative than their approach.

- Remove AdaptiveSignals type, readAdaptiveSignals(),
  resolveAdaptiveRoomTimelineLimit(), applyRoomTimelineLimit()
- Remove adaptiveTimeline and configuredTimelineLimit class fields
- Remove adaptiveTimeline from SlidingSyncDiagnostics type and UI
- Stop unsubscribing on nav in useSlidingSyncActiveRoom
- Lower PRUNE_TIMELINE_THRESHOLD from 150 to 100
Wire onMembershipLeave to RoomMemberEvent.Membership in attach()/dispose().
When the local user leaves or is banned from a subscribed room, calls
unsubscribeFromRoom() which prunes timelines exceeding PRUNE_TIMELINE_THRESHOLD.

Previously, unsubscribeFromRoom() was dead code — nothing in the app called it.

Also add 5 unit tests covering: leave, ban, other-user leave, join (no-op),
and non-subscribed room (no-op).
@Just-Insane Just-Insane changed the title fix: stop sliding sync on dispose and prune inactive room timelines fix: tighten sliding sync memory management Mar 19, 2026
@Just-Insane Just-Insane marked this pull request as ready for review March 19, 2026 03:54
@Just-Insane
Copy link
Copy Markdown
Contributor Author

Did limited testing on the PR Preview, however, I'm not in that many large, active rooms, so my ability to test is limited. I will try leaving it open overnight to see what happens.

Overall, it seems more stable when switching between rooms, which is a major win imo.

@github-actions
Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

Status Preview URL Commit Alias Updated (UTC)
✅ Deployment successful! https://pr-348-sable.raspy-dream-bb1d.workers.dev fa4da19 pr-348 Thu, 19 Mar 2026 05:18:37 GMT

@Just-Insane
Copy link
Copy Markdown
Contributor Author

After leaving the app open overnight (though, my rooms aren't overly active), it seems RAM usage is lower:

image

@dozro dozro enabled auto-merge March 19, 2026 14:01
@dozro dozro added this pull request to the merge queue Mar 19, 2026
Merged via the queue into dev with commit 0fcc38c Mar 19, 2026
9 checks passed
@dozro dozro deleted the fix/sliding-sync-memory branch March 27, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants