80381e06a573 | atagen | 2026-10-25 03:02 UTC | 1 | use std::{ |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 2 | collections::HashSet, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 3 | sync::{ |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 4 | Arc, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 5 | atomic::{AtomicBool, Ordering}, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 6 | }, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 7 | time::Duration, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 8 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 9 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 10 | use matrix_sdk::{Room, deserialized_responses::TimelineEvent, room::MessagesOptions}; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 11 | use matrix_sdk_ui::sync_service::SyncService; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 12 | use ruma::{OwnedEventId, time::Instant}; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 13 | use tokio::sync::{OwnedMutexGuard, mpsc, oneshot, watch}; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 14 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 15 | use super::{ |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 16 | executor::{JoinHandle, spawn}, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 17 | timeline::SessionContext, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 18 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 19 | use crate::{ |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 20 | CommandOutput, Error, ErrorKind, Event, HistoryContinuity, HistoryRepairBudget, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 21 | HistoryRepairCommand, HistoryRepairPhase, HistoryRepairPolicy, HistoryRepairReport, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 22 | HistoryRepairStop, HistoryRepairStrategy, transport::Responder, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 23 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 24 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 25 | const PAGE_SIZE: u16 = 50; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 26 | const STAGING_LIMIT: usize = 10_000; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 27 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 28 | pub(super) struct InstallRequest { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 29 | pub(super) events: Vec<TimelineEvent>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 30 | pub(super) prev_batch: Option<String>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 31 | pub(super) reply: oneshot::Sender<Result<(), Error>>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 32 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 33 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 34 | struct Stage { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 35 | events: Vec<TimelineEvent>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 36 | cursor: Option<String>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 37 | anchor: Option<OwnedEventId>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 38 | report: HistoryRepairReport, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 39 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 40 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 41 | struct Running { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 42 | task: JoinHandle<Stage>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 43 | cancel: watch::Sender<bool>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 44 | reply: Responder, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 45 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 46 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 47 | pub(super) struct RepairController { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 48 | room: Room, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 49 | context: SessionContext, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 50 | installs: mpsc::Sender<InstallRequest>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 51 | requests: mpsc::Receiver<InstallRequest>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 52 | running: Option<Running>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 53 | stage: Option<Stage>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 54 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 55 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 56 | impl RepairController { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 57 | pub(super) fn new(room: Room, context: SessionContext) -> Self { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 58 | let (installs, requests) = mpsc::channel(1); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 59 | Self { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 60 | room, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 61 | context, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 62 | installs, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 63 | requests, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 64 | running: None, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 65 | stage: None, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 66 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 67 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 68 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 69 | pub(super) fn is_running(&self) -> bool { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 70 | self.running.is_some() |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 71 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 72 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 73 | pub(super) async fn execute(&mut self, command: HistoryRepairCommand, reply: Responder) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 74 | if matches!(command, HistoryRepairCommand::Cancel { .. }) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 75 | if let Some(running) = &self.running { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 76 | running.cancel.send_replace(true); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 77 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 78 | reply.done(Ok(())); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 79 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 80 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 81 | if self.is_running() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 82 | reply.done(Err(Error::new( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 83 | ErrorKind::RoomBusy, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 84 | "room history repair is running", |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 85 | ))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 86 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 87 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 88 | let (stage, budget) = match command { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 89 | HistoryRepairCommand::Start { policy, budget, .. } => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 90 | if let Err(error) = validate_budget(&budget) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 91 | reply.done(Err(error)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 92 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 93 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 94 | let policy = match policy { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 95 | Some(policy) => policy, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 96 | None => match self.context.mentions.repair_policy().await { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 97 | Ok(policy) => policy, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 98 | Err(error) => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 99 | reply.done(Err(error)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 100 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 101 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 102 | }, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 103 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 104 | let coverage = match self |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 105 | .context |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 106 | .mentions |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 107 | .repair_coverage(self.room.room_id().to_owned()) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 108 | .await |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 109 | { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 110 | Ok(coverage) => coverage, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 111 | Err(error) => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 112 | reply.done(Err(error)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 113 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 114 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 115 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 116 | let duration = coverage.duration_ms(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 117 | let report = HistoryRepairReport { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 118 | room_id: self.room.room_id().to_owned(), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 119 | requested_policy: policy.clone(), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 120 | strategy: HistoryRepairStrategy::Recent, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 121 | coverage_duration_ms: duration, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 122 | threshold_evaluated: duration.is_some() |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 123 | && coverage.unresolved |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 124 | && matches!(policy, HistoryRepairPolicy::BridgeShortGaps { .. }), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 125 | pages: 0, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 126 | events: 0, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 127 | phase: HistoryRepairPhase::FetchingRecent, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 128 | continuity: HistoryContinuity::Unresolved, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 129 | recent_available: false, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 130 | stop: None, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 131 | failure: None, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 132 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 133 | ( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 134 | Stage { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 135 | events: Vec::new(), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 136 | cursor: None, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 137 | anchor: None, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 138 | report, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 139 | }, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 140 | budget, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 141 | ) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 142 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 143 | HistoryRepairCommand::Continue { budget, .. } => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 144 | if let Err(error) = validate_budget(&budget) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 145 | reply.done(Err(error)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 146 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 147 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 148 | let Some(mut stage) = self.stage.take() else { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 149 | reply.done(Err(Error::new( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 150 | ErrorKind::Invalid, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 151 | "no history bridge to continue", |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 152 | ))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 153 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 154 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 155 | if !stage.report.recent_available |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 156 | || stage.report.continuity != HistoryContinuity::Unresolved |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 157 | { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 158 | reply.done(Err(Error::new( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 159 | ErrorKind::Invalid, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 160 | "history bridge is not resumable", |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 161 | ))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 162 | self.stage = Some(stage); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 163 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 164 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 165 | stage.report.strategy = HistoryRepairStrategy::Bridge; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 166 | stage.report.phase = HistoryRepairPhase::Bridging; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 167 | stage.report.stop = None; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 168 | stage.report.failure = None; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 169 | (stage, budget) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 170 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 171 | HistoryRepairCommand::StartRecent { .. } => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 172 | let Some(mut stage) = self.stage.take() else { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 173 | reply.done(Err(Error::new( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 174 | ErrorKind::Invalid, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 175 | "no recent history repair", |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 176 | ))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 177 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 178 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 179 | if !stage.report.recent_available { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 180 | self.stage = Some(stage); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 181 | reply.done(Err(Error::new( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 182 | ErrorKind::Invalid, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 183 | "recent history is not available, retry the repair", |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 184 | ))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 185 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 186 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 187 | stage.report.strategy = HistoryRepairStrategy::Recent; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 188 | stage.report.phase = HistoryRepairPhase::Ready; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 189 | stage.report.stop = None; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 190 | stage.report.failure = None; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 191 | let report = stage.report.clone(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 192 | let published = self |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 193 | .context |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 194 | .events |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 195 | .publish_repair(report.clone()) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 196 | .await |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 197 | .map_err(|_| Error::interrupted()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 198 | reply.send(published.map(|_| CommandOutput::HistoryRepair(report))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 199 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 200 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 201 | _ => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 202 | reply.done(Err(Error::new( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 203 | ErrorKind::Invalid, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 204 | "repair command was misrouted", |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 205 | ))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 206 | return; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 207 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 208 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 209 | let (cancel, receiver) = watch::channel(false); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 210 | let room = self.room.clone(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 211 | let context = self.context.clone(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 212 | let installs = self.installs.clone(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 213 | let task = |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 214 | spawn(async move { run(room, context, installs, receiver, stage, budget).await }); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 215 | self.running = Some(Running { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 216 | task, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 217 | cancel, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 218 | reply, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 219 | }); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 220 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 221 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 222 | pub(super) async fn next_install(&mut self) -> Option<InstallRequest> { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 223 | let running = self.running.as_mut()?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 224 | tokio::select! { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 225 | request = self.requests.recv() => request, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 226 | result = &mut running.task => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 227 | let running = self.running.take().expect("repair task exists"); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 228 | match result { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 229 | Ok(stage) => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 230 | let report = stage.report.clone(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 231 | let published = self.context.events.publish_repair(report.clone()).await |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 232 | .map_err(|_| Error::interrupted()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 233 | running.reply.send(published.map(|_| CommandOutput::HistoryRepair(report))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 234 | self.stage = Some(stage); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 235 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 236 | Err(_) => running.reply.done(Err(Error::interrupted())), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 237 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 238 | None |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 239 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 240 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 241 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 242 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 243 | pub(super) async fn shutdown(&mut self) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 244 | if let Some(mut running) = self.running.take() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 245 | running.task.abort(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 246 | match (&mut running.task).await { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 247 | Ok(stage) => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 248 | let report = stage.report; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 249 | let published = self |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 250 | .context |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 251 | .events |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 252 | .publish_repair(report.clone()) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 253 | .await |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 254 | .map_err(|_| Error::interrupted()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 255 | running |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 256 | .reply |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 257 | .send(published.map(|_| CommandOutput::HistoryRepair(report))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 258 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 259 | Err(_) => running.reply.done(Err(Error::interrupted())), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 260 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 261 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 262 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 263 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 264 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 265 | impl Drop for RepairController { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 266 | fn drop(&mut self) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 267 | if let Some(running) = &self.running { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 268 | running.task.abort(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 269 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 270 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 271 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 272 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 273 | fn validate_budget(budget: &HistoryRepairBudget) -> Result<(), Error> { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 274 | if !(1..=100).contains(&budget.max_pages) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 275 | || !(50..=10_000).contains(&budget.max_events) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 276 | || !(1..=60_000).contains(&budget.max_duration_ms) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 277 | { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 278 | return Err(Error::new( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 279 | ErrorKind::Invalid, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 280 | "repair budget requires 1 to 100 pages, 50 to 10000 events, and 1 to 60000 milliseconds", |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 281 | )); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 282 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 283 | Ok(()) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 284 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 285 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 286 | fn should_bridge(policy: &HistoryRepairPolicy, duration: Option<u64>, boundary: bool) -> bool { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 287 | match policy { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 288 | HistoryRepairPolicy::Bridge => true, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 289 | HistoryRepairPolicy::StartRecent => false, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 290 | HistoryRepairPolicy::BridgeShortGaps { threshold_ms } => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 291 | boundary |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 292 | && *threshold_ms > 0 |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 293 | && duration.is_some_and(|duration| duration <= *threshold_ms) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 294 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 295 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 296 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 297 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 298 | async fn run( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 299 | room: Room, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 300 | context: SessionContext, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 301 | installs: mpsc::Sender<InstallRequest>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 302 | mut cancel: watch::Receiver<bool>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 303 | mut stage: Stage, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 304 | budget: HistoryRepairBudget, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 305 | ) -> Stage { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 306 | let started = Instant::now(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 307 | let initial_pages = stage.report.pages; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 308 | let initial_events = stage.report.events; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 309 | let result = async { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 310 | if !stage.report.recent_available { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 311 | let (cache, _handles) = room.event_cache().await.map_err(cache_error)?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 312 | stage.anchor = cache |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 313 | .events() |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 314 | .await |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 315 | .map_err(cache_error)? |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 316 | .iter() |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 317 | .find_map(TimelineEvent::event_id); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 318 | let _ = context |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 319 | .events |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 320 | .send(Event::HistoryRepairChanged(stage.report.clone())) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 321 | .await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 322 | let guard = pause_sync(&context, &mut cancel, &started, &budget).await?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 323 | let page = fetch(&room, None, &mut cancel, &started, &budget).await?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 324 | stage.report.pages += 1; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 325 | stage.report.events += page.chunk.len() as u32; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 326 | stage.cursor = page.end; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 327 | stage.events = page.chunk.into_iter().rev().collect(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 328 | deduplicate(&mut stage.events); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 329 | install(&installs, &stage).await?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 330 | stage.report.recent_available = true; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 331 | if contains_anchor(&stage) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 332 | stage.report.strategy = HistoryRepairStrategy::Overlap; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 333 | stage.report.continuity = HistoryContinuity::Verified; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 334 | } else if stage.cursor.is_none() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 335 | stage.report.continuity = HistoryContinuity::AccessibleStart; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 336 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 337 | if stage.report.continuity != HistoryContinuity::Unresolved { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 338 | context |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 339 | .mentions |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 340 | .repair_resolved(room.room_id().to_owned()) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 341 | .await?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 342 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 343 | guard.finish().await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 344 | let _ = context |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 345 | .events |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 346 | .send(Event::HistoryRepairChanged(stage.report.clone())) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 347 | .await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 348 | if stage.report.continuity != HistoryContinuity::Unresolved { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 349 | return Ok(()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 350 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 351 | if !should_bridge( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 352 | &stage.report.requested_policy, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 353 | stage.report.coverage_duration_ms, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 354 | stage.report.threshold_evaluated, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 355 | ) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 356 | return Ok(()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 357 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 358 | stage.report.strategy = HistoryRepairStrategy::Bridge; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 359 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 360 | stage.report.phase = HistoryRepairPhase::Bridging; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 361 | let _ = context |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 362 | .events |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 363 | .send(Event::HistoryRepairChanged(stage.report.clone())) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 364 | .await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 365 | if contains_anchor(&stage) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 366 | stage.report.continuity = HistoryContinuity::Verified; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 367 | } else if stage.cursor.is_none() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 368 | stage.report.continuity = HistoryContinuity::AccessibleStart; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 369 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 370 | while stage.report.continuity == HistoryContinuity::Unresolved { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 371 | if *cancel.borrow() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 372 | stage.report.stop = Some(HistoryRepairStop::Cancelled); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 373 | break; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 374 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 375 | if stage.report.pages - initial_pages + 1 >= u32::from(budget.max_pages) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 376 | || stage.report.events - initial_events + 2 * u32::from(PAGE_SIZE) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 377 | > budget.max_events |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 378 | || stage.events.len() + 2 * usize::from(PAGE_SIZE) > STAGING_LIMIT |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 379 | || started.elapsed() >= Duration::from_millis(budget.max_duration_ms) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 380 | { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 381 | stage.report.stop = Some(HistoryRepairStop::Budget); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 382 | break; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 383 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 384 | if stage.anchor.is_none() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 385 | stage.report.stop = Some(HistoryRepairStop::NoAnchor); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 386 | break; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 387 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 388 | let Some(cursor) = stage.cursor.clone() else { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 389 | break; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 390 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 391 | let page = fetch(&room, Some(&cursor), &mut cancel, &started, &budget).await?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 392 | stage.report.pages += 1; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 393 | stage.report.events += page.chunk.len() as u32; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 394 | if page.end.as_deref() == Some(&cursor) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 395 | stage.report.stop = Some(HistoryRepairStop::CursorStalled); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 396 | break; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 397 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 398 | let mut older: Vec<_> = page.chunk.into_iter().rev().collect(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 399 | older.append(&mut stage.events); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 400 | deduplicate(&mut older); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 401 | stage.events = older; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 402 | stage.cursor = page.end; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 403 | if contains_anchor(&stage) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 404 | stage.report.continuity = HistoryContinuity::Verified; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 405 | } else if stage.cursor.is_none() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 406 | stage.report.continuity = HistoryContinuity::AccessibleStart; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 407 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 408 | if stage.report.continuity == HistoryContinuity::Unresolved { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 409 | let _ = context |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 410 | .events |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 411 | .send(Event::HistoryRepairChanged(stage.report.clone())) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 412 | .await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 413 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 414 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 415 | if stage.report.continuity != HistoryContinuity::Unresolved |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 416 | && stage.report.strategy == HistoryRepairStrategy::Bridge |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 417 | { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 418 | let guard = pause_sync(&context, &mut cancel, &started, &budget).await?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 419 | let head = fetch(&room, None, &mut cancel, &started, &budget).await?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 420 | stage.report.pages += 1; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 421 | stage.report.events += head.chunk.len() as u32; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 422 | let head: Vec<_> = head.chunk.into_iter().rev().collect(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 423 | if !extend_head(&mut stage.events, head) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 424 | stage.report.continuity = HistoryContinuity::Unresolved; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 425 | stage.report.stop = Some(HistoryRepairStop::HistoryUnavailable); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 426 | return Ok(()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 427 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 428 | install(&installs, &stage).await?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 429 | context |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 430 | .mentions |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 431 | .repair_resolved(room.room_id().to_owned()) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 432 | .await?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 433 | guard.finish().await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 434 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 435 | Ok::<_, Error>(()) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 436 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 437 | .await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 438 | if let Err(error) = result { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 439 | if error.kind == ErrorKind::Interrupted { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 440 | stage.report.stop = Some(HistoryRepairStop::Cancelled); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 441 | } else if error.kind == ErrorKind::RoomBusy { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 442 | stage.report.stop = Some(HistoryRepairStop::Budget); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 443 | } else { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 444 | stage.report.failure = Some(error); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 445 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 446 | stage.report.continuity = HistoryContinuity::Unresolved; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 447 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 448 | stage.report.phase = if stage.report.stop.is_some() || stage.report.failure.is_some() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 449 | HistoryRepairPhase::Paused |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 450 | } else { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 451 | HistoryRepairPhase::Ready |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 452 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 453 | stage |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 454 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 455 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 456 | fn contains_anchor(stage: &Stage) -> bool { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 457 | stage.anchor.as_ref().is_some_and(|anchor| { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 458 | stage |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 459 | .events |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 460 | .iter() |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 461 | .any(|event| event.event_id().as_ref() == Some(anchor)) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 462 | }) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 463 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 464 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 465 | fn deduplicate(events: &mut Vec<TimelineEvent>) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 466 | let mut seen = HashSet::new(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 467 | events.retain(|event| event.event_id().is_some_and(|id| seen.insert(id))); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 468 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 469 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 470 | fn extend_head(events: &mut Vec<TimelineEvent>, head: Vec<TimelineEvent>) -> bool { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 471 | let ids: HashSet<_> = events.iter().filter_map(TimelineEvent::event_id).collect(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 472 | let Some(overlap) = head |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 473 | .iter() |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 474 | .position(|event| event.event_id().is_some_and(|id| ids.contains(&id))) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 475 | else { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 476 | return events.is_empty() && head.is_empty(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 477 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 478 | events.extend(head.into_iter().skip(overlap + 1)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 479 | deduplicate(events); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 480 | true |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 481 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 482 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 483 | async fn install(installs: &mpsc::Sender<InstallRequest>, stage: &Stage) -> Result<(), Error> { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 484 | let (reply, response) = oneshot::channel(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 485 | installs |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 486 | .send(InstallRequest { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 487 | events: stage.events.clone(), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 488 | prev_batch: stage.cursor.clone(), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 489 | reply, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 490 | }) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 491 | .await |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 492 | .map_err(|_| Error::interrupted())?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 493 | response.await.map_err(|_| Error::interrupted())? |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 494 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 495 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 496 | async fn fetch( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 497 | room: &Room, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 498 | from: Option<&str>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 499 | cancel: &mut watch::Receiver<bool>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 500 | started: &Instant, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 501 | budget: &HistoryRepairBudget, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 502 | ) -> Result<matrix_sdk::room::Messages, Error> { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 503 | let remaining = Duration::from_millis(budget.max_duration_ms).saturating_sub(started.elapsed()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 504 | if *cancel.borrow() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 505 | return Err(Error::interrupted()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 506 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 507 | let mut options = MessagesOptions::backward().from(from); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 508 | options.limit = PAGE_SIZE.into(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 509 | tokio::select! { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 510 | _ = cancel.changed() => Err(Error::interrupted()), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 511 | result = matrix_sdk::timeout::timeout(room.messages(options), remaining) => { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 512 | let page = result.map_err(|_| Error::new(ErrorKind::RoomBusy, "repair work duration exhausted"))? |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 513 | .map_err(|source| Error::from_sdk("fetching repair history failed", source))?; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 514 | if page.chunk.len() > usize::from(PAGE_SIZE) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 515 | return Err(Error::new(ErrorKind::Invalid, "server exceeded the repair page limit")); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 516 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 517 | Ok(page) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 518 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 519 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 520 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 521 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 522 | struct SyncPause { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 523 | sync: Arc<SyncService>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 524 | shutdown: Arc<AtomicBool>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 525 | gate: Option<OwnedMutexGuard<()>>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 526 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 527 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 528 | impl SyncPause { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 529 | async fn finish(mut self) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 530 | if !self.shutdown.load(Ordering::Acquire) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 531 | self.sync.start().await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 532 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 533 | self.gate.take(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 534 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 535 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 536 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 537 | impl Drop for SyncPause { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 538 | fn drop(&mut self) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 539 | if let Some(gate) = self.gate.take() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 540 | let sync = self.sync.clone(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 541 | let shutdown = self.shutdown.clone(); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 542 | spawn(async move { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 543 | if !shutdown.load(Ordering::Acquire) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 544 | sync.start().await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 545 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 546 | drop(gate); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 547 | }); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 548 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 549 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 550 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 551 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 552 | async fn pause_sync( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 553 | context: &SessionContext, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 554 | cancel: &mut watch::Receiver<bool>, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 555 | started: &Instant, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 556 | budget: &HistoryRepairBudget, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 557 | ) -> Result<SyncPause, Error> { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 558 | let remaining = Duration::from_millis(budget.max_duration_ms).saturating_sub(started.elapsed()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 559 | let gate = tokio::select! { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 560 | _ = cancel.changed() => return Err(Error::interrupted()), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 561 | result = matrix_sdk::timeout::timeout(context.repair_gate.clone().lock_owned(), remaining) => |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 562 | result.map_err(|_| Error::new(ErrorKind::RoomBusy, "repair work duration exhausted"))?, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 563 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 564 | if context.repair_shutdown.load(Ordering::Acquire) { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 565 | return Err(Error::interrupted()); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 566 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 567 | let guard = SyncPause { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 568 | sync: context.sync.clone(), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 569 | shutdown: context.repair_shutdown.clone(), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 570 | gate: Some(gate), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 571 | }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 572 | context.sync.stop().await; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 573 | Ok(guard) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 574 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 575 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 576 | fn cache_error(source: matrix_sdk::event_cache::EventCacheError) -> Error { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 577 | Error::with_source( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 578 | ErrorKind::Store, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 579 | "room history cache operation failed", |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 580 | source, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 581 | ) |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 582 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 583 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 584 | #[cfg(test)] |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 585 | mod tests { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 586 | use super::*; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 587 | |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 588 | #[test] |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 589 | fn threshold_requires_known_coverage_and_an_independent_boundary() { |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 590 | let policy = HistoryRepairPolicy::BridgeShortGaps { threshold_ms: 100 }; |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 591 | assert!(should_bridge(&policy, Some(100), true)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 592 | assert!(should_bridge(&policy, Some(99), true)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 593 | assert!(!should_bridge(&policy, Some(101), true)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 594 | assert!(!should_bridge(&policy, None, true)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 595 | assert!(!should_bridge(&policy, Some(1), false)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 596 | assert!(!should_bridge( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 597 | &HistoryRepairPolicy::BridgeShortGaps { threshold_ms: 0 }, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 598 | Some(0), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 599 | true |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 600 | )); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 601 | assert!(should_bridge(&HistoryRepairPolicy::Bridge, None, false)); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 602 | assert!(!should_bridge( |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 603 | &HistoryRepairPolicy::StartRecent, |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 604 | Some(1), |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 605 | true |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 606 | )); |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 607 | } |
80381e06a573 | atagen | 2026-10-25 03:02 UTC | 608 | } |