Skip to content

feat(sdk): Live updates for pinned events timeline with replacements and redactions #3840

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 23, 2024

Conversation

jmartinesp
Copy link
Contributor

@jmartinesp jmartinesp commented Aug 14, 2024

This is a follow-up PR for #3870.

Changes

  • Make TimelineEventBuilder process synced events for TimelineFocus::PinnedEvents too.
  • Use the relationship cache added in sdk: add relationships cache to EventCache #3870 to load pinned events and other events related to them so their final timeline event results contain the changes those would add (edits, redactions, reactions, etc.).
  • Make Room::event_with_context also save its results to the cache.
  • Add new tests for pinned events and related events.
  • Fix the pinned events benchmark in room_bench.

The end result of this is the pinned events timeline now reacts to edits and redactions, and those values are kept even when the event list reloads.

  • Public API changes documented in changelogs (optional)

Signed-off-by:

@jmartinesp jmartinesp requested a review from a team as a code owner August 14, 2024 11:49
@jmartinesp jmartinesp requested review from Hywan and removed request for a team August 14, 2024 11:49
Copy link

codecov bot commented Aug 14, 2024

Codecov Report

Attention: Patch coverage is 83.33333% with 9 lines in your changes missing coverage. Please review.

Project coverage is 84.08%. Comparing base (fb63c8d) to head (7cfcbae).
Report is 7 commits behind head on main.

Files Patch % Lines
crates/matrix-sdk/src/event_cache/paginator.rs 28.57% 5 Missing ⚠️
...matrix-sdk-ui/src/timeline/pinned_events_loader.rs 89.47% 2 Missing ⚠️
crates/matrix-sdk-ui/src/timeline/event_handler.rs 50.00% 1 Missing ⚠️
crates/matrix-sdk/src/event_cache/mod.rs 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3840      +/-   ##
==========================================
+ Coverage   84.06%   84.08%   +0.01%     
==========================================
  Files         266      266              
  Lines       27779    27812      +33     
==========================================
+ Hits        23352    23385      +33     
  Misses       4427     4427              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jmartinesp jmartinesp force-pushed the jme/update-pinned-events branch from 664fb74 to 00199d9 Compare August 14, 2024 12:14
@bnjbvr bnjbvr requested review from bnjbvr and removed request for Hywan August 14, 2024 12:25
Copy link
Member

@bnjbvr bnjbvr left a comment

Choose a reason for hiding this comment

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

Looks like you're on the right track! I'd like a few test changes, and the main logic in the EventCache itself isn't tested independently. Please add some tests for that too, since it's a different layer than the timeline.

Also, please reword the PR title to follow our conventional commit guidelines :)
Thanks!

Comment on lines 80 to 83
if let Err(err) = semaphore.acquire().await {
warn!("error when loading pinned event: {err}");
return None;
}
Copy link
Member

Choose a reason for hiding this comment

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

Pretty sure this is a no-op when the semaphore could be acquired, because the guard (in the Ok() value) is not bound, so it's immediately dropped. Can you add a test for this? (I would even recommend making a separate PR for this feature, since it seems unrelated to the current PR's purpose, and could land separately.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have no idea how to test that, to be honest. Since there is no timeout I think the execution will just get stuck there until a new permit is available.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I replaced this with a buffered stream now. It seems to work just as fine and there is no need for the semaphore.

@@ -396,6 +404,49 @@ impl EventCacheInner {
continue;
};

// Handle and cache events and relations
let mut relations = self.relations.write().await;
Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately, this is not the right place to add this; I suppose this should be in the same place where I inserted events into Self::events. Follow the breadcrumbs :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I'll have to do that in a separate method then? I don't think I can have 2 RwLockWriteGuards at the same time, one for all_events and another one for relations, so I don't think I'll be able to add this on append_events_locked_impl.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I just saw #3840 (comment).

Comment on lines 442 to 448
let mut events = self.events.write().await;
for ev in &joined_room_update.timeline.events {
if let Some(ev_id) = ev.event_id() {
events.insert(ev_id, (room_id.clone(), ev.clone()));
}
}
drop(events);
Copy link
Member

Choose a reason for hiding this comment

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

This can entirely be removed, because it's already done a few layers below.

@@ -501,6 +558,29 @@ impl RoomEventCache {
None
}

/// Try to find an event by id in this room, along with all relations.
pub async fn event_with_relations(&self, event_id: &EventId) -> Vec<SyncTimelineEvent> {
Copy link
Member

Choose a reason for hiding this comment

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

Can you return an (SyncTimelineEvent, Vec<SyncTimelineEvent>) instead, so it's clear where the target event is? (Right now there's nothing either in the signature or the doc-comment that makes it obvious it's the first one.)

/// Try to find an event by id in this room, along with all relations.
pub async fn event_with_relations(&self, event_id: &EventId) -> Vec<SyncTimelineEvent> {
let relations = self.inner.relations.read().await;
let relation_ids = relations.get(event_id);
Copy link
Member

Choose a reason for hiding this comment

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

Can you inline this where it's used, instead? It's not useful to preload it, if the event couldn't be found.

test_helper.server.reset().await;

// Load new pinned event contents from sync, where $2 is and edit on $1
let _ = Box::pin(
Copy link
Member

Choose a reason for hiding this comment

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

Why is the Box::pin needed? Can you do without?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, leftover from one of my tests to check why the tests were stuck.

.await;

// The list is reloaded, so it's reset
assert_matches!(timeline_stream.next().await.unwrap(), VectorDiff::Clear);
Copy link
Member

Choose a reason for hiding this comment

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

I think there's an assert_next that exists and could replace most of these assert_matches invokes here and below, and in the other test(s) too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For some reason changing assert_matches!(timeline_stream.next().await.unwrap(), *) to assert_next_matches!(timeline_stream, *) makes one of the tests fail with:

assertion failed: stream is not ready, it's pending

Maybe I need to add a delay before? It's quite weird.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, it's because under the hood it calls Stream::next_now_or_never... That seems dangerous. I don't see any similar function for doing it while awaiting for the next item.

Copy link
Member

Choose a reason for hiding this comment

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

We need to add a timeout then, otherwise if the stream item doesn't come immediately, then the test will be timing out with cargo-nextest's delay, which is a minute or longer.

Copy link
Contributor Author

@jmartinesp jmartinesp Aug 20, 2024

Choose a reason for hiding this comment

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

Added a new assert_next_matches_with_timeout! macro for this. It's based on the assert_next_matches, but instead of checking the immediate result of the stream, it adds a small timeout which can be customised: b1fe70a

relations
.insert(orig_ev_id.to_owned(), BTreeSet::from_iter(vec![related_ev_id.to_owned()]));
}
drop(relations);
Copy link
Member

Choose a reason for hiding this comment

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

nit: no need to drop explicitly, it'll happen automatically at the end of the function's body.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, copy&paste issue. It's gone now.

@@ -496,6 +504,18 @@ impl EventCacheInner {
}
}
}

async fn insert_or_update_relationship(&self, orig_ev_id: &EventId, related_ev_id: &EventId) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: please fully spell out event_id for consistency, and original too

(I for one would call these two parameters original and related, because including the parameter's type is spurious, since the parameter type is next to the variable name, but that's my preference :))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm removing this code so it shouldn't be needed anymore.

@@ -186,6 +187,8 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {

group.bench_function(BenchmarkId::new("load_pinned_events", name), |b| {
b.to_async(&runtime).iter(|| async {
client.event_cache().subscribe().unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

Is there really a need to do it for each iteration of the benchmark? I don't think so, it should be subscribed to once and for all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems like client.event_cache().subscribe().unwrap(); does some async operations, and the benchmark panics with:

there is no reactor running, must be called from the context of a Tokio 1.x runtime

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe I can wrap Client in an Arc and use runtime.spawn_blocking to run that code? Is there a better solution?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tried fixing this in 807c875, please let me know if there's a better solution.

@jmartinesp jmartinesp changed the title Live update pinned events timeline with replacements and redactions feat(sdk): Live updates for pinned events timeline with replacements and redactions Aug 14, 2024
@jmartinesp jmartinesp force-pushed the jme/update-pinned-events branch from d9880bd to b4f147f Compare August 20, 2024 08:46
@jmartinesp
Copy link
Contributor Author

So, funny thing: it seems like when the devs reported /event didn't return the sender info it was a mistake. So I'm wondering if I should go back to using /event for loading the pinned events since it was faster...

@jmartinesp jmartinesp requested a review from bnjbvr August 20, 2024 09:58
@jmartinesp jmartinesp force-pushed the jme/update-pinned-events branch from a1d1a90 to b93ed11 Compare August 20, 2024 11:22
@jmartinesp jmartinesp marked this pull request as draft August 21, 2024 09:09
@jmartinesp jmartinesp force-pushed the jme/update-pinned-events branch from b93ed11 to 7f97fba Compare August 21, 2024 12:31
@jmartinesp jmartinesp changed the base branch from main to feat/jme/add-event-relationships-cache August 21, 2024 12:32
@jmartinesp jmartinesp force-pushed the jme/update-pinned-events branch from 7f97fba to 335f94b Compare August 21, 2024 12:37
@jmartinesp
Copy link
Contributor Author

Ok, so I re-used this PR for holding only the part of the code related to the live updates to the pinned events timeline. It depends on a previous PR being merged and it'll have a following PR containing the semaphore/buffered futures changes.

@jmartinesp jmartinesp marked this pull request as ready for review August 21, 2024 12:38
@jmartinesp jmartinesp force-pushed the jme/update-pinned-events branch from 335f94b to c86c842 Compare August 21, 2024 13:00
Base automatically changed from feat/jme/add-event-relationships-cache to main August 22, 2024 12:24
@jmartinesp jmartinesp force-pushed the jme/update-pinned-events branch from c86c842 to b8f24bd Compare August 22, 2024 12:51
@jmartinesp
Copy link
Contributor Author

The PR this one depended on has been merged and this one has been rebased, so it's ready to review again.

@jmartinesp jmartinesp force-pushed the jme/update-pinned-events branch from b8f24bd to b6f4748 Compare August 23, 2024 08:15
@jmartinesp
Copy link
Contributor Author

jmartinesp commented Aug 23, 2024

Well, apparently #3879 just broke this PR, another rebase will be needed.

EDIT: hoping 7e51a78 fixes it 🤞 .

Copy link
Member

@Hywan Hywan left a comment

Choose a reason for hiding this comment

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

We are almost good!

Comment on lines 589 to 590
// This doesn't insert the event into the linked chunk. In the future there'll
// be no distinction between the linked chunk and the separate cache.
Copy link
Member

Choose a reason for hiding this comment

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

Why?
Can we add a TODO and open an issue to track this please?

Copy link
Contributor Author

@jmartinesp jmartinesp Aug 23, 2024

Choose a reason for hiding this comment

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

The comment is just a copy of the one above it written by @bnjbvr since it follows the same logic, but I think it's probably because we lack the context of where the LinkedChunk containing events to save would go, or if it even makes sense to add the events to one (these events can come from very different sources and may not be related enough to be added as a chunk of a timeline, if I understood how LinkedChunk works properly).

I guess the 'in the future' part means once we have a persistent event cache, but I'm not 100% sure.

Anyway, can create an issue for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Hywan I added the TODO here and opened the issue here.

Copy link
Member

Choose a reason for hiding this comment

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

Excellent, thanks!

@jmartinesp jmartinesp requested a review from Hywan August 23, 2024 10:12
Copy link
Member

@Hywan Hywan left a comment

Choose a reason for hiding this comment

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

Good to me, thanks! Please rebase and you can merge.

This way any reactions/redactions/edits, etc. will be taken into account when building the timeline event.
This commit contains a new `assert_next_matches_with_timeout!` macro that will take a `Stream` and wait for a little while until its next item is ready, then match it to a pattern.
…g the same `RwLock` for the cache, which should be faster
…::event`, save its results to the event cache too.
@jmartinesp jmartinesp force-pushed the jme/update-pinned-events branch from 4f57f54 to 7cfcbae Compare August 23, 2024 11:58
@jmartinesp jmartinesp merged commit c78639d into main Aug 23, 2024
38 checks passed
@jmartinesp jmartinesp deleted the jme/update-pinned-events branch August 23, 2024 12:12
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