atagen

macltok

public
Star0
Watch0

Commit 2c13c8db6174

tarball

atagen <boss@atagen.co>2026-10-25 09:15 UTC

4 files changed+17-280
mentions: drop the stored history repair policy and repair coverage

The policy was echoed into the inbox and never read, and RepairCoverage, GetRepairCoverage and RepairResolved had no reader outside the mentions actor and no public output. A sync discontinuity now shows only as its gap. Only compiles together with the change that removes HistoryRepairCommand::GetPolicy and SetPolicy from the engine.
Modified file: docs/mentions-discovery.md+3 additions−3 deletions
@@ -98,7 +98,7 @@ The checkpoint keeps the position the committed response started from as well
9898 as the one it ended at. A crash after the commit but before the SDK saves `pos`
9999 replays from that earlier position, and the replay is a superset of what was
100100 committed, so it is not treated as a discontinuity. Any other starting position
101-records a `SyncDiscontinuity` gap and marks every observed room unresolved.
101+records a `SyncDiscontinuity` gap.
102102
103103 The hook never fails sync. A failed write keeps the inbox in memory, records a
104104 `StoreFailed` gap, acks, and retries the write a minute later, so a restart
@@ -281,8 +281,8 @@ session resume.
281281
282282 The store holds two records. `inbox` carries entries, saved events, context
283283 windows, candidates, edits, redactions, coverage and recovery cursors. `sync`
284-carries the connection checkpoints, observed rooms, per-room repair coverage
285-and the inbox revision it belongs to. Every response writes `sync`, and
284+carries the connection checkpoints, observed rooms and the inbox
285+revision it belongs to. Every response writes `sync`, and
286286 `inbox` only when the response changed it, both in one commit. A quiet
287287 response therefore costs one small record, while a changed inbox is still
288288 reserialised and re-encrypted whole. Background completions are applied in
Modified file: src/mentions/service.rs+13 additions−119 deletions
@@ -14,7 +14,7 @@ use matrix_sdk::{
1414 sliding_sync::{ResponseCommitBatch, ResponseCommitHook},
1515 };
1616 use ruma::{
17- OwnedEventId, OwnedRoomId, OwnedUserId, RoomId,
17+ OwnedRoomId, OwnedUserId, RoomId,
1818 events::{
1919 AnySyncTimelineEvent, ignored_user_list::IgnoredUserListEventContent,
2020 room::encrypted::OriginalSyncRoomEncryptedEvent,
@@ -24,13 +24,11 @@ use ruma::{
2424 use serde_json::Value;
2525 use tokio::sync::{mpsc, oneshot, watch};
2626
27-use super::state::{
28- Candidate, Checkpoint, Data, RepairCoverage, SavedEvent, SyncState, Window, id, invalid, now,
29-};
27+use super::state::{Candidate, Checkpoint, Data, SavedEvent, SyncState, Window, id, invalid, now};
3028 use crate::{
31- CommandOutput, EngineConfig, Error, ErrorKind, Event, HistoryRepairPolicy, MentionCaptureState,
32- MentionEntry, MentionGapReason, MentionKey, MentionReason, MentionsCommand, MentionsPage,
33- MentionsUpdate, TimelineContent, TimelineItem,
29+ CommandOutput, EngineConfig, Error, ErrorKind, Event, MentionCaptureState, MentionEntry,
30+ MentionGapReason, MentionKey, MentionReason, MentionsCommand, MentionsPage, MentionsUpdate,
31+ TimelineContent, TimelineItem,
3432 engine::{MediaService, MentionsStore, context, executor::EngineBoxFuture},
3533 normalise::{MediaRegistration, normalise_observed},
3634 transport::EventSender,
@@ -55,10 +53,6 @@ enum Request {
5553 MentionsCommand,
5654 oneshot::Sender<Result<CommandOutput, Error>>,
5755 ),
58- GetRepairPolicy(oneshot::Sender<Result<HistoryRepairPolicy, Error>>),
59- SetRepairPolicy(HistoryRepairPolicy, oneshot::Sender<Result<(), Error>>),
60- GetRepairCoverage(OwnedRoomId, oneshot::Sender<Result<RepairCoverage, Error>>),
61- RepairResolved(OwnedRoomId, oneshot::Sender<Result<(), Error>>),
6256 Stop(oneshot::Sender<()>),
6357 }
6458
@@ -134,45 +128,6 @@ impl Handle {
134128 response.await.map_err(|_| Error::interrupted())?
135129 }
136130
137- pub(crate) async fn repair_policy(&self) -> Result<HistoryRepairPolicy, Error> {
138- let (reply, response) = oneshot::channel();
139- self.requests
140- .send(Request::GetRepairPolicy(reply))
141- .await
142- .map_err(|_| Error::interrupted())?;
143- response.await.map_err(|_| Error::interrupted())?
144- }
145-
146- pub(crate) async fn set_repair_policy(&self, policy: HistoryRepairPolicy) -> Result<(), Error> {
147- let (reply, response) = oneshot::channel();
148- self.requests
149- .send(Request::SetRepairPolicy(policy, reply))
150- .await
151- .map_err(|_| Error::interrupted())?;
152- response.await.map_err(|_| Error::interrupted())?
153- }
154-
155- pub(crate) async fn repair_coverage(
156- &self,
157- room_id: OwnedRoomId,
158- ) -> Result<RepairCoverage, Error> {
159- let (reply, response) = oneshot::channel();
160- self.requests
161- .send(Request::GetRepairCoverage(room_id, reply))
162- .await
163- .map_err(|_| Error::interrupted())?;
164- response.await.map_err(|_| Error::interrupted())?
165- }
166-
167- pub(crate) async fn repair_resolved(&self, room_id: OwnedRoomId) -> Result<(), Error> {
168- let (reply, response) = oneshot::channel();
169- self.requests
170- .send(Request::RepairResolved(room_id, reply))
171- .await
172- .map_err(|_| Error::interrupted())?;
173- response.await.map_err(|_| Error::interrupted())?
174- }
175-
176131 pub(crate) async fn stop(&self) {
177132 let (reply, response) = oneshot::channel();
178133 if self.requests.send(Request::Stop(reply)).await.is_ok() {
@@ -407,19 +362,12 @@ impl Service {
407362 Ok(())
408363 }
409364
410- async fn apply(&mut self, data: Option<Data>, sync: Option<SyncState>) -> Result<(), Error> {
411- let dirty = self.dirty;
412- let previous_data = data.map(|data| std::mem::replace(&mut self.data, data));
413- let previous_sync = sync.map(|sync| std::mem::replace(&mut self.sync, sync));
414- self.dirty |= previous_data.is_some();
365+ async fn apply(&mut self, data: Data) -> Result<(), Error> {
366+ let dirty = std::mem::replace(&mut self.dirty, true);
367+ let previous = std::mem::replace(&mut self.data, data);
415368 let result = self.flush().await;
416369 if result.is_err() {
417- if let Some(data) = previous_data {
418- self.data = data;
419- }
420- if let Some(sync) = previous_sync {
421- self.sync = sync;
422- }
370+ self.data = previous;
423371 self.dirty = dirty;
424372 }
425373 result
@@ -433,23 +381,6 @@ impl Service {
433381 self.publish().await;
434382 }
435383
436- async fn set_repair_policy(&mut self, policy: HistoryRepairPolicy) -> Result<(), Error> {
437- let mut next = self.data.clone();
438- next.repair_policy = policy;
439- self.apply(Some(next), None).await
440- }
441-
442- async fn repair_resolved(&mut self, room_id: OwnedRoomId) -> Result<(), Error> {
443- let mut sync = self.sync.clone();
444- let coverage = sync.repair_coverage.entry(room_id.clone()).or_default();
445- coverage.boundary_ms = None;
446- coverage.last_confirmed_ms = Some(now());
447- coverage.reliable = false;
448- coverage.unresolved = false;
449- sync.observed_rooms.insert(room_id);
450- self.apply(None, Some(sync)).await
451- }
452-
453384 async fn command(&mut self, command: MentionsCommand) -> Result<CommandOutput, Error> {
454385 match command {
455386 MentionsCommand::Page { cursor, limit } => {
@@ -496,7 +427,7 @@ impl Service {
496427 start: None,
497428 end: None,
498429 });
499- self.apply(Some(next), None).await?;
430+ self.apply(next).await?;
500431 let (context, media) = self.data.context(&key)?;
501432 self.media.register(media).await;
502433 Ok(CommandOutput::MentionContext(context))
@@ -509,7 +440,7 @@ impl Service {
509440 entry.revision = self.data.revision + 1;
510441 }
511442 }
512- self.apply(Some(next), None).await?;
443+ self.apply(next).await?;
513444 Ok(CommandOutput::Done)
514445 }
515446 MentionsCommand::ReadThrough(boundary) => {
@@ -525,7 +456,7 @@ impl Service {
525456 entry.read = true;
526457 entry.revision = self.data.revision + 1;
527458 }
528- self.apply(Some(next), None).await?;
459+ self.apply(next).await?;
529460 Ok(CommandOutput::Done)
530461 }
531462 MentionsCommand::Recover => {
@@ -535,7 +466,7 @@ impl Service {
535466 let mut next = self.data.clone();
536467 next.coverage.recovering = true;
537468 next.coverage.historical_supported = None;
538- self.apply(Some(next), None).await?;
469+ self.apply(next).await?;
539470 Ok(CommandOutput::Done)
540471 }
541472 }
@@ -562,7 +493,6 @@ impl Service {
562493 if self.timed_out.swap(false, Ordering::AcqRel) {
563494 next.gap(None, MentionGapReason::HookTimedOut, None);
564495 }
565- let observed_at_ms = now();
566496 // a crash after the commit but before the sdk saved `pos` replays from the
567497 // previous position, and that response is a superset of the one committed
568498 let continuous = match &checkpoint {
@@ -574,10 +504,6 @@ impl Service {
574504 };
575505 if !continuous {
576506 next.gap(None, MentionGapReason::SyncDiscontinuity, None);
577- let observed_rooms: Vec<_> = sync.observed_rooms.iter().cloned().collect();
578- for room_id in observed_rooms {
579- Self::mark_unresolved(&mut sync, &room_id, observed_at_ms);
580- }
581507 }
582508 for (room_id, raw) in &batch.raw_rooms {
583509 if raw.limited && sync.observed_rooms.contains(room_id) {
@@ -599,37 +525,18 @@ impl Service {
599525 if self.observable(room_id).is_none() {
600526 continue;
601527 }
602- let previously_observed = sync.observed_rooms.contains(room_id);
603- let limited = batch.raw_rooms.get(room_id).is_some_and(|raw| raw.limited);
604- if !continuous || limited || !previously_observed {
605- Self::mark_unresolved(&mut sync, room_id, observed_at_ms);
606- } else {
607- let coverage = sync.repair_coverage.entry(room_id.clone()).or_default();
608- if !coverage.unresolved {
609- coverage.last_confirmed_ms = Some(observed_at_ms);
610- }
611- coverage.reliable = false;
612- }
613528 sync.observed_rooms.insert(room_id.clone());
614529 let mut observed = BTreeMap::new();
615- let mut latest_anchor = None;
616530 if let Some(raw) = batch.raw_rooms.get(room_id) {
617531 for event in &raw.timeline {
618532 let Ok(value) = serde_json::from_str::<Value>(event.json().get()) else {
619533 continue;
620534 };
621535 if let Some(event_id) = value["event_id"].as_str() {
622- latest_anchor = event_id.parse::<OwnedEventId>().ok();
623536 observed.insert(event_id.to_owned(), value);
624537 }
625538 }
626539 }
627- if let Some(latest_anchor) = latest_anchor {
628- sync.repair_coverage
629- .entry(room_id.clone())
630- .or_default()
631- .latest_anchor = Some(latest_anchor);
632- }
633540 let processed = batch
634541 .response
635542 .rooms
@@ -710,15 +617,6 @@ impl Service {
710617 }
711618 }
712619
713- fn mark_unresolved(sync: &mut SyncState, room_id: &OwnedRoomId, observed_at_ms: u64) {
714- let coverage = sync.repair_coverage.entry(room_id.clone()).or_default();
715- if !coverage.unresolved {
716- coverage.boundary_ms = Some(observed_at_ms);
717- coverage.unresolved = true;
718- }
719- coverage.reliable = false;
720- }
721-
722620 fn filter_ignored(&self, next: &mut Data) -> bool {
723621 let removed: Vec<_> = next
724622 .entries
@@ -1114,10 +1012,6 @@ impl Service {
11141012 }
11151013 Some(Request::Batch(batch, reply)) => { self.batch(batch).await; let _ = reply.send(()); }
11161014 Some(Request::Command(command, reply)) => { let result = self.command(command).await; let _ = reply.send(result); }
1117- Some(Request::GetRepairPolicy(reply)) => { let _ = reply.send(Ok(self.data.repair_policy.clone())); }
1118- Some(Request::SetRepairPolicy(policy, reply)) => { let result = self.set_repair_policy(policy).await; let _ = reply.send(result); }
1119- Some(Request::GetRepairCoverage(room_id, reply)) => { let coverage = self.sync.repair_coverage.get(&room_id).cloned().unwrap_or_default(); let _ = reply.send(Ok(coverage)); }
1120- Some(Request::RepairResolved(room_id, reply)) => { let result = self.repair_resolved(room_id).await; let _ = reply.send(result); }
11211015 None => return,
11221016 },
11231017 Some(result) = work.next(), if !work.is_empty() => {
Modified file: src/mentions/service_tests.rs−136 deletions
@@ -353,142 +353,6 @@ async fn an_unreadable_store_resets_behind_a_gap() {
353353 }
354354
355355 #[tokio::test]
356-async fn repair_policy_and_coverage_roundtrip_through_the_inbox() {
357- let server = MatrixMockServer::new().await;
358- let room_id = room_id!("!repair-coverage:localhost");
359- let (client, _) = joined_client(&server, room_id).await;
360- let config = config();
361- let store = MentionsStore::open(&config, account(&client), MAX_BYTES)
362- .await
363- .unwrap();
364- let media = Arc::new(MediaService::new(
365- client.clone(),
366- EngineConfig::default().max_media_bytes,
367- EngineConfig::default().max_media_sources,
368- ));
369- let (handle, mut service, _) = Service::new(
370- client.clone(),
371- media,
372- Some(Ok(store)),
373- EngineConfig::default(),
374- )
375- .await
376- .unwrap();
377- service.recovery_budget = 0;
378- service.latest_scan = false;
379- let service_task = tokio::spawn(service.run());
380-
381- let default_policy = HistoryRepairPolicy::default();
382- assert_eq!(handle.repair_policy().await.unwrap(), default_policy);
383- let stored_policy = HistoryRepairPolicy::Bridge;
384- handle
385- .set_repair_policy(stored_policy.clone())
386- .await
387- .unwrap();
388- handle.repair_resolved(room_id.to_owned()).await.unwrap();
389- let coverage = handle.repair_coverage(room_id.to_owned()).await.unwrap();
390- assert!(coverage.last_confirmed_ms.is_some());
391- assert!(!coverage.reliable);
392- assert_eq!(coverage.duration_ms(), None);
393-
394- handle.stop().await;
395- service_task.await.unwrap();
396- drop(handle);
397-
398- let reopened = MentionsStore::open(&config, account(&client), MAX_BYTES)
399- .await
400- .unwrap();
401- let persisted: Data = reopened.read("inbox").unwrap().unwrap();
402- assert_eq!(persisted.repair_policy, stored_policy);
403- let sync: SyncState = reopened.read("sync").unwrap().unwrap();
404- let coverage = sync.repair_coverage.get(room_id).unwrap();
405- assert!(coverage.last_confirmed_ms.is_some());
406- assert!(coverage.boundary_ms.is_none());
407- assert!(!coverage.unresolved);
408- assert!(!coverage.reliable);
409- drop(reopened);
410- config.delete().await.unwrap();
411-}
412-
413-#[tokio::test]
414-async fn repair_limited_sync_freezes_the_last_room_checkpoint() {
415- let server = MatrixMockServer::new().await;
416- let room_id = room_id!("!repair-boundary:localhost");
417- let (client, _) = joined_client(&server, room_id).await;
418- let media = Arc::new(MediaService::new(
419- client.clone(),
420- EngineConfig::default().max_media_bytes,
421- EngineConfig::default().max_media_sources,
422- ));
423- let (_handle, mut service, _) = Service::new(client, media, None, EngineConfig::default())
424- .await
425- .unwrap();
426- service.sync.checkpoints.insert(
427- "mentions-connection".to_owned(),
428- Checkpoint {
429- pos: "position-before".to_owned(),
430- previous: None,
431- },
432- );
433- service.sync.observed_rooms.insert(room_id.to_owned());
434- service
435- .sync
436- .repair_coverage
437- .entry(room_id.to_owned())
438- .or_default()
439- .last_confirmed_ms = Some(1_000);
440-
441- let events = [
442- EventFactory::new()
443- .room(room_id)
444- .text_msg("older event")
445- .sender(user_id!("@sender:localhost"))
446- .event_id(event_id!("$repair-older:localhost"))
447- .into_raw_sync(),
448- EventFactory::new()
449- .room(room_id)
450- .text_msg("latest event")
451- .sender(user_id!("@sender:localhost"))
452- .event_id(event_id!("$repair-latest:localhost"))
453- .into_raw_sync(),
454- ];
455- let timeline: Vec<_> = events
456- .iter()
457- .map(|event| serde_json::to_value(event).unwrap())
458- .collect();
459- let raw_room = serde_json::from_value(json!({
460- "limited": true,
461- "prev_batch": "repair-gap-token",
462- "timeline": timeline,
463- }))
464- .unwrap();
465- let batch = ResponseCommitBatch {
466- connection_id: "mentions-connection".to_owned(),
467- previous_pos: Some("position-before".to_owned()),
468- next_pos: "position-after".to_owned(),
469- response: Default::default(),
470- raw_rooms: [(room_id.to_owned(), raw_room)].into(),
471- };
472-
473- service.batch(batch).await;
474-
475- let coverage = service.sync.repair_coverage.get(room_id).unwrap();
476- assert_eq!(coverage.last_confirmed_ms, Some(1_000));
477- assert!(
478- coverage
479- .boundary_ms
480- .is_some_and(|boundary| boundary > 1_000)
481- );
482- assert!(coverage.unresolved);
483- assert!(!coverage.reliable);
484- assert_eq!(coverage.duration_ms(), None);
485- assert_eq!(
486- coverage.latest_anchor.as_deref(),
487- Some(event_id!("$repair-latest:localhost"))
488- );
489-}
490-
491-#[tokio::test]
492356 async fn service_recovers_notification_history_into_durable_unread_inbox() {
493357 let server = MatrixMockServer::new().await;
494358 let room_id = room_id!("!mentions:localhost");
Modified file: src/mentions/state.rs+1 additions−22 deletions
@@ -1,6 +1,6 @@
11 use std::collections::{BTreeMap, BTreeSet};
22
3-use ruma::{OwnedEventId, OwnedUserId};
3+use ruma::OwnedUserId;
44 use serde_json::Value;
55
66 use crate::{
@@ -67,25 +67,6 @@ pub(super) struct Window {
6767 pub end: Option<String>,
6868 }
6969
70-#[derive(Clone, Debug, Default, PartialEq)]
71-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72-pub(crate) struct RepairCoverage {
73- pub(crate) last_confirmed_ms: Option<u64>,
74- pub(crate) boundary_ms: Option<u64>,
75- pub(crate) reliable: bool,
76- pub(crate) unresolved: bool,
77- pub(crate) latest_anchor: Option<OwnedEventId>,
78-}
79-
80-impl RepairCoverage {
81- pub(crate) fn duration_ms(&self) -> Option<u64> {
82- if !self.reliable {
83- return None;
84- }
85- self.boundary_ms?.checked_sub(self.last_confirmed_ms?)
86- }
87-}
88-
8970 #[derive(Clone, Default, PartialEq)]
9071 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9172 pub(super) struct Data {
@@ -103,7 +84,6 @@ pub(super) struct Data {
10384 pub latest_cursor: Option<String>,
10485 pub latest_anchor: Option<String>,
10586 pub latest_head: Option<String>,
106- pub repair_policy: crate::HistoryRepairPolicy,
10787 }
10888
10989 #[derive(Clone, Default, PartialEq)]
@@ -112,7 +92,6 @@ pub(super) struct SyncState {
11292 pub inbox_revision: u64,
11393 pub checkpoints: BTreeMap<String, Checkpoint>,
11494 pub observed_rooms: BTreeSet<ruma::OwnedRoomId>,
115- pub repair_coverage: BTreeMap<ruma::OwnedRoomId, RepairCoverage>,
11695 }
11796
11897 #[derive(Clone, Debug, PartialEq)]