Skip to content

Commit c1bf467

Browse files
committed
fix(sdk): Respect server ordering for remote echoes
1 parent 718864b commit c1bf467

File tree

3 files changed

+98
-13
lines changed

3 files changed

+98
-13
lines changed

crates/matrix-sdk/src/room/timeline/event_handler.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -531,21 +531,29 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> {
531531
{
532532
// TODO: Check whether anything is different about the
533533
// old and new item?
534-
self.timeline_items.set_cloned(idx, item);
535-
return;
534+
// Remove local echo, remote echo will be added below
535+
self.timeline_items.remove(idx);
536536
} else {
537537
warn!(
538538
"Received event with transaction ID, but didn't \
539539
find matching timeline item"
540540
);
541541
}
542-
}
543-
544-
if let Some((idx, old_item)) = find_event_by_id(self.timeline_items, event_id) {
542+
} else if let Some((idx, _old_item)) =
543+
find_local_echo_by_event_id(self.timeline_items, event_id)
544+
{
545545
// This occurs very often right now due to a sliding-sync
546546
// bug: https://github.com/matrix-org/sliding-sync/issues/3
547-
// TODO: Use warn log level once that bug is fixed.
548-
trace!(
547+
// TODO: Remove this branch once the bug is fixed?
548+
trace!("Received remote echo without transaction ID");
549+
self.timeline_items.remove(idx);
550+
} else if let Some((idx, old_item)) =
551+
find_event_by_id(self.timeline_items, event_id)
552+
{
553+
// TODO: Remove for better performance? Doing another scan
554+
// over all the items if the event is not a remote echo is
555+
// slow.
556+
warn!(
549557
?item,
550558
?old_item,
551559
"Received event with an ID we already have a timeline item for"
@@ -677,6 +685,19 @@ fn _update_timeline_item(
677685
}
678686
}
679687

688+
fn find_local_echo_by_event_id<'a>(
689+
lock: &'a [Arc<TimelineItem>],
690+
event_id: &EventId,
691+
) -> Option<(usize, &'a EventTimelineItem)> {
692+
lock.iter()
693+
.enumerate()
694+
.filter_map(|(idx, item)| Some((idx, item.as_event()?)))
695+
// Note: not using event_id() method as this is only supposed to find
696+
// local echoes, where the event ID is stored in the separate field
697+
// rather than the key.
698+
.rfind(|(_, it)| it.event_id.as_deref() == Some(event_id))
699+
}
700+
680701
/// Converts a timestamp since Unix Epoch to a local date and time.
681702
fn timestamp_to_local_datetime(ts: MilliSecondsSinceUnixEpoch) -> DateTime<Local> {
682703
Local

crates/matrix-sdk/src/room/timeline/tests.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,67 @@ async fn remote_echo_without_txn_id() {
412412
}))
413413
.await;
414414

415-
let item =
416-
assert_matches!(stream.next().await, Some(VecDiff::UpdateAt { value, index: 1 }) => value);
415+
// The local echo is removed
416+
assert_matches!(stream.next().await, Some(VecDiff::Pop { .. }));
417+
// This day divider shouldn't be present, or the previous one should be
418+
// removed. There being a two day dividers in a row is a bug, but it's
419+
// non-trivial to fix and rare enough that we can fix it later (only happens
420+
// when the first message on a given day is a local echo).
421+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
422+
423+
// … and the remote echo is added.
424+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
425+
assert_matches!(item.as_event().unwrap().key(), TimelineKey::EventId(_));
426+
}
427+
428+
#[async_test]
429+
async fn remote_echo_new_position() {
430+
let timeline = TestTimeline::new();
431+
let mut stream = timeline.stream();
432+
433+
// Given a local event…
434+
let txn_id = timeline
435+
.handle_local_event(AnyMessageLikeEventContent::RoomMessage(
436+
RoomMessageEventContent::text_plain("echo"),
437+
))
438+
.await;
439+
440+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
441+
442+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
443+
let txn_id_from_event = assert_matches!(
444+
item.as_event().unwrap().key(),
445+
TimelineKey::TransactionId(txn_id) => txn_id
446+
);
447+
assert_eq!(txn_id, *txn_id_from_event);
448+
449+
// … and another event that comes back before the remote echo
450+
timeline.handle_live_message_event(&BOB, RoomMessageEventContent::text_plain("test")).await;
451+
let _day_divider = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
452+
let _bob_message = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
453+
454+
// When the remote echo comes in…
455+
timeline
456+
.handle_live_custom_event(json!({
457+
"content": {
458+
"body": "echo",
459+
"msgtype": "m.text",
460+
},
461+
"sender": &*ALICE,
462+
"event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I",
463+
"origin_server_ts": 6,
464+
"type": "m.room.message",
465+
"unsigned": {
466+
"transaction_id": txn_id,
467+
},
468+
}))
469+
.await;
470+
471+
// … the local echo should be removed
472+
assert_matches!(stream.next().await, Some(VecDiff::RemoveAt { index: 1 }));
473+
474+
// … and the remote echo added
475+
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
417476
assert_matches!(item.as_event().unwrap().key(), TimelineKey::EventId(_));
418477
}
419478

crates/matrix-sdk/tests/integration/room/timeline.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,15 @@ async fn echo() {
227227
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
228228
server.reset().await;
229229

230-
let remote_echo = assert_matches!(
231-
timeline_stream.next().await,
232-
Some(VecDiff::UpdateAt { index: 1, value }) => value
233-
);
230+
// Local echo is removed
231+
assert_matches!(timeline_stream.next().await, Some(VecDiff::Pop { .. }));
232+
// Bug, will be fixed later. See comment in remote_echo_without_txn_id test
233+
// from `room::timeline::tests`.
234+
let _day_divider =
235+
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
236+
237+
let remote_echo =
238+
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
234239
let item = remote_echo.as_event().unwrap();
235240
assert!(item.event_id().is_some());
236241
assert!(item.is_own());

0 commit comments

Comments
 (0)