atagen

matrix-rust-sdk

public
Star0
Watch0

Commit 2a65be55072a

tarball

atagen <boss@atagen.co>2026-10-25 08:58 UTC

8 files changed+36-17
event-cache: bound visited cursors and tidy small history leftovers

visited_cursors gained a cursor per page forever and was re-encrypted with every gap rewrite, while a cycle detector only needs the recent ones. RoomEventCache::clear now reports a panicked or cancelled task as EventCacheError::TaskInterrupted instead of panicking the caller, sqlite always writes the gap object so only the legacy reader remains, and the Cinny shortcode field gets a name.
Modified file: crates/matrix-sdk-base/src/event_cache/mod.rs+1 additions−1 deletions
@@ -45,7 +45,7 @@ pub struct HistoryGap {
4545 pub older_boundary: Option<OwnedEventId>,
4646 #[doc = "The original event on the newer side, preserved as pages are inserted."]
4747 pub newer_boundary: Option<OwnedEventId>,
48- #[doc = "Previously consumed cursors detect server pagination cycles across restarts."]
48+ #[doc = "The most recently consumed cursors detect server pagination cycles across restarts."]
4949 #[serde(default, skip_serializing_if = "Vec::is_empty")]
5050 pub visited_cursors: Vec<String>,
5151 #[doc = "Unavailable and joined intervals remain distinguishable after restart."]
Modified file: crates/matrix-sdk-sqlite/src/event_cache_store.rs+3 additions−8 deletions
@@ -389,11 +389,11 @@ impl TransactionExtForLinkedChunks for Transaction<'_> {
389389 #[serde(untagged)]
390390 enum StoredGap {
391391 Legacy(String),
392- History(Gap),
392+ Current(Gap),
393393 }
394394 Ok(match serde_json::from_slice(&prev_token_bytes)? {
395395 StoredGap::Legacy(token) => Gap { token, history: None },
396- StoredGap::History(gap) => gap,
396+ StoredGap::Current(gap) => gap,
397397 })
398398 }
399399
@@ -697,12 +697,7 @@ impl EventCacheStore for SqliteEventCacheStore {
697697 }
698698
699699 Update::NewGapChunk { previous, new, next, gap } => {
700- let serialized = if gap.history.is_some() {
701- serde_json::to_vec(&gap)?
702- } else {
703- serde_json::to_vec(&gap.token)?
704- };
705- let prev_token = this.encode_value(serialized)?;
700+ let prev_token = this.encode_value(serde_json::to_vec(&gap)?)?;
706701
707702 let previous = previous.as_ref().map(ChunkIdentifier::index);
708703 let new = new.index();
Modified file: crates/matrix-sdk-ui/src/timeline/controller/mod.rs+2 additions−2 deletions
@@ -72,7 +72,7 @@ use super::{
7272 TimelineFocus, TimelineItem, TimelineItemContent, TimelineItemKind,
7373 TimelineReadReceiptTracking, VirtualTimelineItem,
7474 algorithms::{rfind_event_by_id, rfind_event_item},
75- event_handler::{REACTION_SHORTCODE_FIELD, reaction_shortcode},
75+ event_handler::{CINNY_REACTION_SHORTCODE_FIELD, REACTION_SHORTCODE_FIELD, reaction_shortcode},
7676 event_item::{ReactionStatus, RemoteEventOrigin},
7777 item::TimelineUniqueId,
7878 subscriber::TimelineSubscriber,
@@ -538,7 +538,7 @@ impl<P: RoomDataProvider> TimelineController<P> {
538538 let content = serde_json::to_value(&content)
539539 .and_then(|mut content| {
540540 content[REACTION_SHORTCODE_FIELD] = shortcode.into();
541- content["shortcode"] = shortcode.into();
541+ content[CINNY_REACTION_SHORTCODE_FIELD] = shortcode.into();
542542 Raw::new(&content)
543543 })
544544 .map_err(|err| {
Modified file: crates/matrix-sdk-ui/src/timeline/event_handler.rs+7 additions−2 deletions
@@ -71,7 +71,12 @@ use crate::{
7171 /// [MSC4027]: https://github.com/matrix-org/matrix-spec-proposals/pull/4027
7272 pub(super) const REACTION_SHORTCODE_FIELD: &str = "com.beeper.reaction.shortcode";
7373
74-/// Reads the [MSC4027] shortcode from a reaction's raw content.
74+/// The unprefixed shortcode field Cinny reads and writes instead of
75+/// [`REACTION_SHORTCODE_FIELD`].
76+pub(super) const CINNY_REACTION_SHORTCODE_FIELD: &str = "shortcode";
77+
78+/// Reads the [MSC4027] shortcode from a reaction's raw content, falling back
79+/// to the field Cinny uses.
7580 ///
7681 /// [MSC4027]: https://github.com/matrix-org/matrix-spec-proposals/pull/4027
7782 pub(super) fn reaction_shortcode(content: &Raw<AnyMessageLikeEventContent>) -> Option<String> {
@@ -79,7 +84,7 @@ pub(super) fn reaction_shortcode(content: &Raw<AnyMessageLikeEventContent>) -> O
7984 .get_field(REACTION_SHORTCODE_FIELD)
8085 .ok()
8186 .flatten()
82- .or_else(|| content.get_field("shortcode").ok().flatten())
87+ .or_else(|| content.get_field(CINNY_REACTION_SHORTCODE_FIELD).ok().flatten())
8388 }
8489
8590 /// When adding an event, useful information related to the source of the event.
Modified file: crates/matrix-sdk/src/event_cache/caches/room/history.rs+5 additions
@@ -26,6 +26,9 @@ pub struct HistoryGapPosition {
2626 pub next_event_id: Option<OwnedEventId>,
2727 }
2828
29+/// A longer cursor cycle goes undetected, which only costs bounded fills.
30+const VISITED_CURSORS: usize = 16;
31+
2932 pub(super) fn gap_positions(candidate: &EventLinkedChunk) -> Vec<HistoryGapPosition> {
3033 let mut positions: Vec<HistoryGapPosition> = Vec::new();
3134 let mut pending = 0;
@@ -594,6 +597,8 @@ fn apply_page(
594597 };
595598 if !history.visited_cursors.contains(&gap.token) {
596599 history.visited_cursors.push(gap.token.clone());
600+ let excess = history.visited_cursors.len().saturating_sub(VISITED_CURSORS);
601+ history.visited_cursors.drain(..excess);
597602 }
598603 if let Some(next) = next {
599604 gap.token = next;
Modified file: crates/matrix-sdk/src/event_cache/caches/room/mod.rs+2 additions−2 deletions
@@ -363,7 +363,7 @@ impl RoomEventCache {
363363 let cache = self.clone();
364364 matrix_sdk_common::executor::spawn(async move { cache.clear_inner().await })
365365 .await
366- .expect("joining room cache clear failed")
366+ .map_err(|error| EventCacheError::TaskInterrupted(error.to_string()))?
367367 }
368368
369369 async fn clear_inner(&self) -> Result<()> {
@@ -416,7 +416,7 @@ impl RoomEventCache {
416416 result
417417 })
418418 .await
419- .expect("joining history reseed failed")
419+ .map_err(|error| EventCacheError::TaskInterrupted(error.to_string()))?
420420 }
421421
422422 /// Return a reference to the state.
Modified file: crates/matrix-sdk/src/event_cache/mod.rs+5 additions
@@ -141,6 +141,11 @@ pub enum EventCacheError {
141141 /// A string containing details about the error.
142142 details: String,
143143 },
144+
145+ /// A cache operation that runs to completion on its own task panicked or
146+ /// was cancelled by a runtime shutdown.
147+ #[error("the event cache task did not complete: {0}")]
148+ TaskInterrupted(String),
144149 }
145150
146151 /// A result using the [`EventCacheError`].
Modified file: crates/matrix-sdk/src/send_queue/mod.rs+11 additions−2 deletions
@@ -3162,6 +3162,15 @@ fn canonicalize_dependent_requests(
31623162 by_txn.into_values().flat_map(|entries| entries.into_iter().cloned()).collect()
31633163 }
31643164
3165+/// The [MSC4027] field holding a custom emote reaction's shortcode.
3166+///
3167+/// [MSC4027]: https://github.com/matrix-org/matrix-spec-proposals/pull/4027
3168+const REACTION_SHORTCODE_FIELD: &str = "com.beeper.reaction.shortcode";
3169+
3170+/// The unprefixed shortcode field Cinny reads instead of
3171+/// [`REACTION_SHORTCODE_FIELD`].
3172+const CINNY_REACTION_SHORTCODE_FIELD: &str = "shortcode";
3173+
31653174 /// Adds the [MSC4027] shortcode of a custom emote to a reaction's content,
31663175 /// which ruma's [`ReactionEventContent`] has no field for.
31673176 ///
@@ -3172,8 +3181,8 @@ fn with_reaction_shortcode(
31723181 ) -> Result<Raw<AnyMessageLikeEventContent>, serde_json::Error> {
31733182 let mut content =
31743183 content.deserialize_as_unchecked::<serde_json::Map<String, serde_json::Value>>()?;
3175- content.insert("com.beeper.reaction.shortcode".to_owned(), shortcode.clone().into());
3176- content.insert("shortcode".to_owned(), shortcode.into());
3184+ content.insert(REACTION_SHORTCODE_FIELD.to_owned(), shortcode.clone().into());
3185+ content.insert(CINNY_REACTION_SHORTCODE_FIELD.to_owned(), shortcode.into());
31773186 Ok(Raw::new(&content)?.cast_unchecked())
31783187 }