Skip to content

feat(sdk): Flatten the hierarchy of caches in the Event Cache, part 1: Thread#6517

Draft
Hywan wants to merge 29 commits intomatrix-org:event-cache-refactoringfrom
Hywan:feat-sdk-event-cache-flatten-threads
Draft

feat(sdk): Flatten the hierarchy of caches in the Event Cache, part 1: Thread#6517
Hywan wants to merge 29 commits intomatrix-org:event-cache-refactoringfrom
Hywan:feat-sdk-event-cache-flatten-threads

Conversation

@Hywan
Copy link
Copy Markdown
Member

@Hywan Hywan commented Apr 28, 2026

This PR constitutes the first part of a series of 2 or 3 PR. It's about flattening the hierarchy of caches (see #6152). It starts with threads.

Why do we want to do that? The room cache owns the thread caches, the pinned-events cache, and the event-focused cache. Not only this is bad from a design point of view, but it also creates bugs. Notably, the room cache has a lock to access its state, that needs to be acquired to access to, for example, the threads, which have their own locks for each of their states. We have a hierarchy of locks and it's easy to get a deadlock (see the very recent #6542 for example).

Also, this hierarchy of caches make it hard to unit test and to share code.

This PR starts by “flattening” the thread caches, i.e. it extracts the threads from the room cache to move them directly in Caches. The tree goes from:

graph TD;
    EventCache-->Caches;
    Caches-->RoomEventCache;
    RoomEventCache-->RoomEventCacheState;
    RoomEventCacheState-->ThreadEventCache;
    ThreadEventCache-->ThreadEventCacheState;
Loading

to:

graph TD;
    EventCache-->Caches;
    Caches-->RoomEventCache;
    Caches-->ThreadEventCache;
    RoomEventCache-->RoomEventCacheState;
    ThreadEventCache-->ThreadEventCacheState;
Loading

Many tests must be updated which increases the diffstat, but do not be afraid, the patches are as small and atomic as possible. This is mostly code moves, except the new aggregator module (see below), which explains the difference between additions and deletions.

Breaking Changes

All methods related to threads on RoomEventCache are removed, for example: RoomEventCache::thread_pagination is now ThreadEventCache::pagination, or RoomEventCache::subscribe_to_thread is now ThreadEventCache::subscribe.

EventCache now provides 2 new methods: room (previously for_room) and thread to access to a RoomEventCache and a ThreadEventCache.

New Features

Because, yeah, we have new features coming with this refactoring!

Pipelines

As anticipated, we need “pipelines” under the form of event_caches::caches::aggregator. Pipelines are actually necessary to make this flattening possible.

An aggregator takes a Timeline and returns a new Timeline tailored for a particular cache. Two aggregators exist so far:

  • aggregate_timeline_for_room,
  • aggregate_timeline_for_threads.

It transforms the “post-processing of events“ that lived in the room cache to a “pre-processing of events”. It allows a couple of nice things, such as:

  • each cache is isolated and works on its own set of events/its own Timeline value,
  • each aggregator is unit testable, which increases confidence of which event goes where,
  • we can decide upfront the destination of each event.

The room cache gets all the events for the moment, it's simpler like this, until threads are always enabled at least. The thread cache initially got all the in-thread events. However, it excluded the edit of in-thread events (because an edit of an in-thread event doesn't have a relation to the thread!), and the redaction of in-thread events. Now, the events for edits and redactions of in-thread events exist in the threads! This is new and it unlocks nice future features, like the fact we can use the matrix_sdk::latest_events API to compute the latest event for the ThreadSummary correctly since we have all the edits, redactions etc.

Improved ThreadSummary

One of the last commit update ThreadSummary to use an internal API of matrix_sdk::latest_events: we don't use the Latest Event API at its maximum potential, but at least it fixes a couple of bugs we had with ThreadSummary. This is not very efficient right now, but I prefer to be correct first. At least, now, we have a single place to think about the latest event!



  • I've documented the public API Changes in the appropriate CHANGELOG.md files.
  • This PR was made with the help of AI.

@Hywan Hywan force-pushed the feat-sdk-event-cache-flatten-threads branch 5 times, most recently from 8109f7f to 0e243c6 Compare April 29, 2026 12:48
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 29, 2026

Merging this PR will not alter performance

✅ 50 untouched benchmarks


Comparing Hywan:feat-sdk-event-cache-flatten-threads (19d6050) with main (6a33b94)1

Open in CodSpeed

Footnotes

  1. No successful run was found on event-cache-refactoring (94716fd) during the generation of this report, so main (6a33b94) was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@Hywan Hywan force-pushed the feat-sdk-event-cache-flatten-threads branch 3 times, most recently from efc8a37 to c9c1f45 Compare May 5, 2026 11:55
@Hywan Hywan changed the title feat(sdk): Flatten the hierarchy of caches in the Event Cache feat(sdk): Flatten the hierarchy of caches in the Event Cache, part 1: Thread May 5, 2026
@Hywan Hywan force-pushed the feat-sdk-event-cache-flatten-threads branch from 98d95f8 to 01ccd32 Compare May 5, 2026 18:29
@Hywan Hywan marked this pull request as ready for review May 6, 2026 09:45
@Hywan Hywan requested a review from a team as a code owner May 6, 2026 09:45
@Hywan Hywan requested review from stefanceriu and removed request for a team May 6, 2026 09:45
@Hywan Hywan marked this pull request as draft May 6, 2026 09:47
Copy link
Copy Markdown
Member

@stefanceriu stefanceriu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is significantly above my pay grade but having read the plan and the implementation, commit by commit, it makes perfect sense and I think it's a great addition/change. Nicely done! 👏

/// an event that is not a thread root.
///
/// [`ThreadEventCache`]: thread::ThreadEventCache
pub async fn thread(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called get or create then? 🤔

Comment thread crates/matrix-sdk/src/event_cache/caches/room/mod.rs
Comment thread crates/matrix-sdk/src/event_cache/mod.rs
Comment thread crates/matrix-sdk-ui/src/timeline/controller/mod.rs
Comment thread crates/matrix-sdk-common/src/serde_helpers.rs
let Some(event_id) =
extract_redaction_target(event.raw(), &self.room_version_rules.redaction)
else {
warn!("missing target event id from the redaction event");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want to print this out of any event that's not a redaction.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good eyes! Removing it.


let mut associated_thread_root = None;

for (thread_root, thread) in existing_threads {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be that the target thread cache doesn't exist locally yet? Does the redaction need to be stored and applied later if that's the case?

Comment thread crates/matrix-sdk/src/event_cache/caches/aggregator.rs
Comment thread crates/matrix-sdk/src/event_cache/caches/thread/state.rs Outdated
///
/// This will remove all the information related to the event cache, in
/// memory and in the persisted storage, if enabled.
pub async fn clear_event_cache_storage(&self) -> Result<(), ClientError> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably add a changelog for this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'll update the changelogs when merging event-cache-refactoring to main to avoid conflicts, but thanks for noticing!

@Hywan Hywan force-pushed the feat-sdk-event-cache-flatten-threads branch from 1e854ef to 28a9795 Compare May 8, 2026 12:17
Hywan added 12 commits May 8, 2026 14:33
This patch implements `StateLock::write_owned` along with the
`OwnedStateLockWriteGuard`. This is going to be useful to acquire an
exclusive owned lock when we will need to reset all the caches.
…o `ThreadEventCacheState` directly.

This method moves a couple of methods to `ThreadEventCacheState` so they
can be shared with other types in the next patches.
This patch implements the exclusive owned lock guard for the thread
cache state.
This patch starts flattening caches by starting the extract thread
caches from the room cache.

This patch comments some methods or adds a `todo!()` to make the code
compile. It's the price for smaller commits.

The thread cache gains new methods like `handle_joined_room_update` or
`handle_joined_left_update`. It mimics the flow in the room cache.

A new `caches::aggregator` module is created to introduce “pipelines”:
a `Timeline` is created for each cache. It lives in front of the room
cache, and basically reverses the “post process” flow to a “pre process”
flow. More commits will improve it.

`ResetCaches` is now also responsible to clear the threads in addition
to the room (previously, the room cache was responsible of that).
This patch removes `RoomEventCache::thread_pagination` as it is now
available on `ThreadEventCache::pagination`.
This patch removes `RoomEventCache::subscribe_to_thread` as it is now
available on `ThreadEventCache::subscribe`.
This patch moves the `find_event` implementation in `persistence` with
the idea to share it with the thread caches.
…ions` to `persistence`.

This patch moves the `find_event_relations` and
`find_event_with_relations` implementations in `persistence` with the
idea to share it with the thread caches.
…oom's post process to thread's post process.

The room is post-processing thread events. This patch moves the
handling of `bunlded_latest_thread_event` from `RoomEventCache` to
`ThreadEventCache`.
…to `ThreadEventCache`.

This patch updates the `ThreadSummary` flow. `Caches` ask each thread to
compute its `ThreadSummary` and ask the room to update/store it.

The edit events targeting in-thread events are now stored in the thread
cache! This is new as it was only stored in the room cache previously.
This unlocks the possibility to correctly compute the latest event for a
thread with the `matrix_sdk::latest_events` API in the future.
…` and `ThreadEventCache`.

This patch handles `m.room.redaction` for thread caches.

This is a naive approach for now to have small commits.
Hywan added 11 commits May 8, 2026 15:07
This patch removes `RoomEventCacheState::threads` along with
`get_or_reload_patch` and `update_threads`.
This patch removes `RoomEventCache::find_event_in_thread` as we now have
`ThreadEventCache::find_event`.
…Guard::post_process_new_events`.

This patch cleans up `post_process_new_events` arguments that were
specific to thread or thread summary. I am aware that R2D2 had special
treatement here, and it's going to be re-introduced later.
This patch adds `EventCache::thread`, sibling of `EventCache::room`.
Finally!
This patch updates `TimelineFocusKind` to hold its own event cache type
for each kind of timeline. The idea is to stop fetching each cache for
every action and makes some path infallible. The performance are also
perceivably faster according to a real test by Jorge on Android.
@Hywan Hywan force-pushed the feat-sdk-event-cache-flatten-threads branch from 28a9795 to 4ef3d0b Compare May 8, 2026 13:09
Hywan added 6 commits May 8, 2026 15:31
…ciated threads.

This patch stores all events with a relation to an in-thread event,
plus the `m.room.redaction` events targeting in-thread events, in their
respective thread cache.

This patch also introduces a new `extract_redaction_target` helper, that
is used in the `maybe_apply_new_redaction` methods of the room and the
thread cache.

The aggregator for the threads is updated to aggregate the
`m.room.redaction` event, and annotation relation type. A refactoring
is done to use a `match` over `RelationType`, instead of using multiple
`extract_*` helper, which clarifies the code I believe. A limitation has
been found and a `TODO` has been added to keep the commits “small”.
…` `Timeline`.

Previously, because a `ThreadEventCache` didn't receive all its events,
a `Timeline` with a `Thread` focus had to be updated via two sources:
updates from `RoomEventCache` and from `ThreadEventCache`. Now that this
problem is solved, i.e. a `ThreadEventCache` contains all its events,
a `Timeline` with a `Thread` focus doesn't have to be updated by two
sources. This is what this patch does.
This patch updates the `compute_thread_summary` method on the thread
cache to use the `matrix_sdk::latest_events` API. It bends it, also
a crime. We don't use it at its full potential, but at least it fixes
several bugs and makes it more aligned with the room latest events: it
skips `m.room.redaction` (new), the majority of the state events (new),
mostly correctly handle edits (new) etc.

This patch also allows the system to erase the `ThreadSummary` if none
can be computed.
@Hywan Hywan force-pushed the feat-sdk-event-cache-flatten-threads branch from 4ef3d0b to 19d6050 Compare May 8, 2026 13:31
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.

2 participants