Skip to content

Commit 82c24a8

Browse files
committed
fix(sdk): Make the pinned events sorting deterministic
The previous sorting logic relied on SystemTime::now, which introduces non-deterministic behavior which might result in a panic.
1 parent 3372ca3 commit 82c24a8

2 files changed

Lines changed: 87 additions & 7 deletions

File tree

  • bindings/matrix-sdk/changelog.d
  • crates/matrix-sdk/src/event_cache/caches/pinned_events
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a panic due to non-deterministic sorting of pinned events.

crates/matrix-sdk/src/event_cache/caches/pinned_events/mod.rs

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use std::{collections::BTreeSet, sync::Arc};
15+
use std::{cmp::Ordering, collections::BTreeSet, sync::Arc};
1616

1717
use futures_util::{StreamExt as _, stream};
1818
use matrix_sdk_base::{
@@ -537,13 +537,92 @@ impl PinnedEventCache {
537537
// them, since we've lost all ordering information from using /event or
538538
// /relations. Resort to sorting using chronological ordering (oldest ->
539539
// newest).
540-
loaded_events.sort_by_key(|item| {
541-
item.raw()
542-
.deserialize()
543-
.map(|e| e.origin_server_ts())
544-
.unwrap_or_else(|_| MilliSecondsSinceUnixEpoch::now())
545-
});
540+
loaded_events.sort_by(compare_pinned_items);
546541

547542
Ok(Some(loaded_events))
548543
}
549544
}
545+
546+
fn compare_pinned_items(a: &Event, b: &Event) -> Ordering {
547+
let a_time: Option<MilliSecondsSinceUnixEpoch> = a.timestamp_raw();
548+
let b_time: Option<MilliSecondsSinceUnixEpoch> = b.timestamp_raw();
549+
550+
compare_by_optional_timestamp(a_time, b_time)
551+
}
552+
553+
fn compare_by_optional_timestamp(
554+
a: Option<MilliSecondsSinceUnixEpoch>,
555+
b: Option<MilliSecondsSinceUnixEpoch>,
556+
) -> Ordering {
557+
match (a, b) {
558+
(None, None) => Ordering::Equal,
559+
(None, Some(_)) => Ordering::Greater,
560+
(Some(_), None) => Ordering::Less,
561+
(Some(a), Some(b)) => a.cmp(&b),
562+
}
563+
}
564+
565+
#[cfg(not(target_family = "wasm"))]
566+
#[cfg(test)]
567+
mod tests {
568+
use proptest::prelude::*;
569+
use ruma::UInt;
570+
571+
use super::*;
572+
573+
fn any_timestamp() -> impl Strategy<Value = Option<MilliSecondsSinceUnixEpoch>> {
574+
prop::option::of(
575+
any::<u32>().prop_map(|value| MilliSecondsSinceUnixEpoch(UInt::from(value))),
576+
)
577+
}
578+
579+
#[test]
580+
fn sort_pinned_events_never_panics_only_nones() {
581+
let mut vec = vec![None; 100_000];
582+
vec.sort_by(|a, b| compare_by_optional_timestamp(*a, *b))
583+
}
584+
585+
proptest! {
586+
#[test]
587+
fn sort_pinned_events_never_panics(mut v in prop::collection::vec(any_timestamp(), 0..1000)) {
588+
v.sort_by(
589+
|a, b| compare_by_optional_timestamp(*a, *b))
590+
}
591+
592+
#[test]
593+
fn compare_pinned_events_reflexive(a in any_timestamp()) {
594+
prop_assert_eq!(compare_by_optional_timestamp(a, a), Ordering::Equal);
595+
}
596+
597+
#[test]
598+
fn compare_pinned_events_antisymmetric(a in any_timestamp(), b in any_timestamp()) {
599+
let ab = compare_by_optional_timestamp(a, b);
600+
let ba = compare_by_optional_timestamp(b, a);
601+
602+
prop_assert_eq!(ab, ba.reverse());
603+
}
604+
605+
#[test]
606+
fn compare_pinned_events_transitive(
607+
a in any_timestamp(),
608+
b in any_timestamp(),
609+
c in any_timestamp()
610+
) {
611+
let ab = compare_by_optional_timestamp(a, b);
612+
let bc = compare_by_optional_timestamp(b, c);
613+
let ac = compare_by_optional_timestamp(a, c);
614+
615+
if ab == Ordering::Less && bc == Ordering::Less {
616+
prop_assert_eq!(ac, Ordering::Less);
617+
}
618+
619+
if ab == Ordering::Equal && bc == Ordering::Equal {
620+
prop_assert_eq!(ac, Ordering::Equal);
621+
}
622+
623+
if ab == Ordering::Greater && bc == Ordering::Greater {
624+
prop_assert_eq!(ac, Ordering::Greater);
625+
}
626+
}
627+
}
628+
}

0 commit comments

Comments
 (0)