atagen

macltok

public
Star0
Watch0
e4bb841a0497f1f169e75a66425e64b6c0981643
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/engine/repair.rs

80381e06a573atagen2026-10-25 03:02 UTC1
use std::{
80381e06a573atagen2026-10-25 03:02 UTC2
    collections::HashSet,
80381e06a573atagen2026-10-25 03:02 UTC3
    sync::{
80381e06a573atagen2026-10-25 03:02 UTC4
        Arc,
80381e06a573atagen2026-10-25 03:02 UTC5
        atomic::{AtomicBool, Ordering},
80381e06a573atagen2026-10-25 03:02 UTC6
    },
80381e06a573atagen2026-10-25 03:02 UTC7
    time::Duration,
80381e06a573atagen2026-10-25 03:02 UTC8
};
80381e06a573atagen2026-10-25 03:02 UTC9
80381e06a573atagen2026-10-25 03:02 UTC10
use matrix_sdk::{Room, deserialized_responses::TimelineEvent, room::MessagesOptions};
80381e06a573atagen2026-10-25 03:02 UTC11
use matrix_sdk_ui::sync_service::SyncService;
80381e06a573atagen2026-10-25 03:02 UTC12
use ruma::{OwnedEventId, time::Instant};
80381e06a573atagen2026-10-25 03:02 UTC13
use tokio::sync::{OwnedMutexGuard, mpsc, oneshot, watch};
80381e06a573atagen2026-10-25 03:02 UTC14
80381e06a573atagen2026-10-25 03:02 UTC15
use super::{
80381e06a573atagen2026-10-25 03:02 UTC16
    executor::{JoinHandle, spawn},
80381e06a573atagen2026-10-25 03:02 UTC17
    timeline::SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC18
};
80381e06a573atagen2026-10-25 03:02 UTC19
use crate::{
80381e06a573atagen2026-10-25 03:02 UTC20
    CommandOutput, Error, ErrorKind, Event, HistoryContinuity, HistoryRepairBudget,
80381e06a573atagen2026-10-25 03:02 UTC21
    HistoryRepairCommand, HistoryRepairPhase, HistoryRepairPolicy, HistoryRepairReport,
80381e06a573atagen2026-10-25 03:02 UTC22
    HistoryRepairStop, HistoryRepairStrategy, transport::Responder,
80381e06a573atagen2026-10-25 03:02 UTC23
};
80381e06a573atagen2026-10-25 03:02 UTC24
80381e06a573atagen2026-10-25 03:02 UTC25
const PAGE_SIZE: u16 = 50;
80381e06a573atagen2026-10-25 03:02 UTC26
const STAGING_LIMIT: usize = 10_000;
80381e06a573atagen2026-10-25 03:02 UTC27
80381e06a573atagen2026-10-25 03:02 UTC28
pub(super) struct InstallRequest {
80381e06a573atagen2026-10-25 03:02 UTC29
    pub(super) events: Vec<TimelineEvent>,
80381e06a573atagen2026-10-25 03:02 UTC30
    pub(super) prev_batch: Option<String>,
80381e06a573atagen2026-10-25 03:02 UTC31
    pub(super) reply: oneshot::Sender<Result<(), Error>>,
80381e06a573atagen2026-10-25 03:02 UTC32
}
80381e06a573atagen2026-10-25 03:02 UTC33
80381e06a573atagen2026-10-25 03:02 UTC34
struct Stage {
80381e06a573atagen2026-10-25 03:02 UTC35
    events: Vec<TimelineEvent>,
80381e06a573atagen2026-10-25 03:02 UTC36
    cursor: Option<String>,
80381e06a573atagen2026-10-25 03:02 UTC37
    anchor: Option<OwnedEventId>,
80381e06a573atagen2026-10-25 03:02 UTC38
    report: HistoryRepairReport,
80381e06a573atagen2026-10-25 03:02 UTC39
}
80381e06a573atagen2026-10-25 03:02 UTC40
80381e06a573atagen2026-10-25 03:02 UTC41
struct Running {
80381e06a573atagen2026-10-25 03:02 UTC42
    task: JoinHandle<Stage>,
80381e06a573atagen2026-10-25 03:02 UTC43
    cancel: watch::Sender<bool>,
80381e06a573atagen2026-10-25 03:02 UTC44
    reply: Responder,
80381e06a573atagen2026-10-25 03:02 UTC45
}
80381e06a573atagen2026-10-25 03:02 UTC46
80381e06a573atagen2026-10-25 03:02 UTC47
pub(super) struct RepairController {
80381e06a573atagen2026-10-25 03:02 UTC48
    room: Room,
80381e06a573atagen2026-10-25 03:02 UTC49
    context: SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC50
    installs: mpsc::Sender<InstallRequest>,
80381e06a573atagen2026-10-25 03:02 UTC51
    requests: mpsc::Receiver<InstallRequest>,
80381e06a573atagen2026-10-25 03:02 UTC52
    running: Option<Running>,
80381e06a573atagen2026-10-25 03:02 UTC53
    stage: Option<Stage>,
80381e06a573atagen2026-10-25 03:02 UTC54
}
80381e06a573atagen2026-10-25 03:02 UTC55
80381e06a573atagen2026-10-25 03:02 UTC56
impl RepairController {
80381e06a573atagen2026-10-25 03:02 UTC57
    pub(super) fn new(room: Room, context: SessionContext) -> Self {
80381e06a573atagen2026-10-25 03:02 UTC58
        let (installs, requests) = mpsc::channel(1);
80381e06a573atagen2026-10-25 03:02 UTC59
        Self {
80381e06a573atagen2026-10-25 03:02 UTC60
            room,
80381e06a573atagen2026-10-25 03:02 UTC61
            context,
80381e06a573atagen2026-10-25 03:02 UTC62
            installs,
80381e06a573atagen2026-10-25 03:02 UTC63
            requests,
80381e06a573atagen2026-10-25 03:02 UTC64
            running: None,
80381e06a573atagen2026-10-25 03:02 UTC65
            stage: None,
80381e06a573atagen2026-10-25 03:02 UTC66
        }
80381e06a573atagen2026-10-25 03:02 UTC67
    }
80381e06a573atagen2026-10-25 03:02 UTC68
80381e06a573atagen2026-10-25 03:02 UTC69
    pub(super) fn is_running(&self) -> bool {
80381e06a573atagen2026-10-25 03:02 UTC70
        self.running.is_some()
80381e06a573atagen2026-10-25 03:02 UTC71
    }
80381e06a573atagen2026-10-25 03:02 UTC72
80381e06a573atagen2026-10-25 03:02 UTC73
    pub(super) async fn execute(&mut self, command: HistoryRepairCommand, reply: Responder) {
80381e06a573atagen2026-10-25 03:02 UTC74
        if matches!(command, HistoryRepairCommand::Cancel { .. }) {
80381e06a573atagen2026-10-25 03:02 UTC75
            if let Some(running) = &self.running {
80381e06a573atagen2026-10-25 03:02 UTC76
                running.cancel.send_replace(true);
80381e06a573atagen2026-10-25 03:02 UTC77
            }
80381e06a573atagen2026-10-25 03:02 UTC78
            reply.done(Ok(()));
80381e06a573atagen2026-10-25 03:02 UTC79
            return;
80381e06a573atagen2026-10-25 03:02 UTC80
        }
80381e06a573atagen2026-10-25 03:02 UTC81
        if self.is_running() {
80381e06a573atagen2026-10-25 03:02 UTC82
            reply.done(Err(Error::new(
80381e06a573atagen2026-10-25 03:02 UTC83
                ErrorKind::RoomBusy,
80381e06a573atagen2026-10-25 03:02 UTC84
                "room history repair is running",
80381e06a573atagen2026-10-25 03:02 UTC85
            )));
80381e06a573atagen2026-10-25 03:02 UTC86
            return;
80381e06a573atagen2026-10-25 03:02 UTC87
        }
80381e06a573atagen2026-10-25 03:02 UTC88
        let (stage, budget) = match command {
80381e06a573atagen2026-10-25 03:02 UTC89
            HistoryRepairCommand::Start { policy, budget, .. } => {
80381e06a573atagen2026-10-25 03:02 UTC90
                if let Err(error) = validate_budget(&budget) {
80381e06a573atagen2026-10-25 03:02 UTC91
                    reply.done(Err(error));
80381e06a573atagen2026-10-25 03:02 UTC92
                    return;
80381e06a573atagen2026-10-25 03:02 UTC93
                }
80381e06a573atagen2026-10-25 03:02 UTC94
                let policy = match policy {
80381e06a573atagen2026-10-25 03:02 UTC95
                    Some(policy) => policy,
80381e06a573atagen2026-10-25 03:02 UTC96
                    None => match self.context.mentions.repair_policy().await {
80381e06a573atagen2026-10-25 03:02 UTC97
                        Ok(policy) => policy,
80381e06a573atagen2026-10-25 03:02 UTC98
                        Err(error) => {
80381e06a573atagen2026-10-25 03:02 UTC99
                            reply.done(Err(error));
80381e06a573atagen2026-10-25 03:02 UTC100
                            return;
80381e06a573atagen2026-10-25 03:02 UTC101
                        }
80381e06a573atagen2026-10-25 03:02 UTC102
                    },
80381e06a573atagen2026-10-25 03:02 UTC103
                };
80381e06a573atagen2026-10-25 03:02 UTC104
                let coverage = match self
80381e06a573atagen2026-10-25 03:02 UTC105
                    .context
80381e06a573atagen2026-10-25 03:02 UTC106
                    .mentions
80381e06a573atagen2026-10-25 03:02 UTC107
                    .repair_coverage(self.room.room_id().to_owned())
80381e06a573atagen2026-10-25 03:02 UTC108
                    .await
80381e06a573atagen2026-10-25 03:02 UTC109
                {
80381e06a573atagen2026-10-25 03:02 UTC110
                    Ok(coverage) => coverage,
80381e06a573atagen2026-10-25 03:02 UTC111
                    Err(error) => {
80381e06a573atagen2026-10-25 03:02 UTC112
                        reply.done(Err(error));
80381e06a573atagen2026-10-25 03:02 UTC113
                        return;
80381e06a573atagen2026-10-25 03:02 UTC114
                    }
80381e06a573atagen2026-10-25 03:02 UTC115
                };
80381e06a573atagen2026-10-25 03:02 UTC116
                let duration = coverage.duration_ms();
80381e06a573atagen2026-10-25 03:02 UTC117
                let report = HistoryRepairReport {
80381e06a573atagen2026-10-25 03:02 UTC118
                    room_id: self.room.room_id().to_owned(),
80381e06a573atagen2026-10-25 03:02 UTC119
                    requested_policy: policy.clone(),
80381e06a573atagen2026-10-25 03:02 UTC120
                    strategy: HistoryRepairStrategy::Recent,
80381e06a573atagen2026-10-25 03:02 UTC121
                    coverage_duration_ms: duration,
80381e06a573atagen2026-10-25 03:02 UTC122
                    threshold_evaluated: duration.is_some()
80381e06a573atagen2026-10-25 03:02 UTC123
                        && coverage.unresolved
80381e06a573atagen2026-10-25 03:02 UTC124
                        && matches!(policy, HistoryRepairPolicy::BridgeShortGaps { .. }),
80381e06a573atagen2026-10-25 03:02 UTC125
                    pages: 0,
80381e06a573atagen2026-10-25 03:02 UTC126
                    events: 0,
80381e06a573atagen2026-10-25 03:02 UTC127
                    phase: HistoryRepairPhase::FetchingRecent,
80381e06a573atagen2026-10-25 03:02 UTC128
                    continuity: HistoryContinuity::Unresolved,
80381e06a573atagen2026-10-25 03:02 UTC129
                    recent_available: false,
80381e06a573atagen2026-10-25 03:02 UTC130
                    stop: None,
80381e06a573atagen2026-10-25 03:02 UTC131
                    failure: None,
80381e06a573atagen2026-10-25 03:02 UTC132
                };
80381e06a573atagen2026-10-25 03:02 UTC133
                (
80381e06a573atagen2026-10-25 03:02 UTC134
                    Stage {
80381e06a573atagen2026-10-25 03:02 UTC135
                        events: Vec::new(),
80381e06a573atagen2026-10-25 03:02 UTC136
                        cursor: None,
80381e06a573atagen2026-10-25 03:02 UTC137
                        anchor: None,
80381e06a573atagen2026-10-25 03:02 UTC138
                        report,
80381e06a573atagen2026-10-25 03:02 UTC139
                    },
80381e06a573atagen2026-10-25 03:02 UTC140
                    budget,
80381e06a573atagen2026-10-25 03:02 UTC141
                )
80381e06a573atagen2026-10-25 03:02 UTC142
            }
80381e06a573atagen2026-10-25 03:02 UTC143
            HistoryRepairCommand::Continue { budget, .. } => {
80381e06a573atagen2026-10-25 03:02 UTC144
                if let Err(error) = validate_budget(&budget) {
80381e06a573atagen2026-10-25 03:02 UTC145
                    reply.done(Err(error));
80381e06a573atagen2026-10-25 03:02 UTC146
                    return;
80381e06a573atagen2026-10-25 03:02 UTC147
                }
80381e06a573atagen2026-10-25 03:02 UTC148
                let Some(mut stage) = self.stage.take() else {
80381e06a573atagen2026-10-25 03:02 UTC149
                    reply.done(Err(Error::new(
80381e06a573atagen2026-10-25 03:02 UTC150
                        ErrorKind::Invalid,
80381e06a573atagen2026-10-25 03:02 UTC151
                        "no history bridge to continue",
80381e06a573atagen2026-10-25 03:02 UTC152
                    )));
80381e06a573atagen2026-10-25 03:02 UTC153
                    return;
80381e06a573atagen2026-10-25 03:02 UTC154
                };
80381e06a573atagen2026-10-25 03:02 UTC155
                if !stage.report.recent_available
80381e06a573atagen2026-10-25 03:02 UTC156
                    || stage.report.continuity != HistoryContinuity::Unresolved
80381e06a573atagen2026-10-25 03:02 UTC157
                {
80381e06a573atagen2026-10-25 03:02 UTC158
                    reply.done(Err(Error::new(
80381e06a573atagen2026-10-25 03:02 UTC159
                        ErrorKind::Invalid,
80381e06a573atagen2026-10-25 03:02 UTC160
                        "history bridge is not resumable",
80381e06a573atagen2026-10-25 03:02 UTC161
                    )));
80381e06a573atagen2026-10-25 03:02 UTC162
                    self.stage = Some(stage);
80381e06a573atagen2026-10-25 03:02 UTC163
                    return;
80381e06a573atagen2026-10-25 03:02 UTC164
                }
80381e06a573atagen2026-10-25 03:02 UTC165
                stage.report.strategy = HistoryRepairStrategy::Bridge;
80381e06a573atagen2026-10-25 03:02 UTC166
                stage.report.phase = HistoryRepairPhase::Bridging;
80381e06a573atagen2026-10-25 03:02 UTC167
                stage.report.stop = None;
80381e06a573atagen2026-10-25 03:02 UTC168
                stage.report.failure = None;
80381e06a573atagen2026-10-25 03:02 UTC169
                (stage, budget)
80381e06a573atagen2026-10-25 03:02 UTC170
            }
80381e06a573atagen2026-10-25 03:02 UTC171
            HistoryRepairCommand::StartRecent { .. } => {
80381e06a573atagen2026-10-25 03:02 UTC172
                let Some(mut stage) = self.stage.take() else {
80381e06a573atagen2026-10-25 03:02 UTC173
                    reply.done(Err(Error::new(
80381e06a573atagen2026-10-25 03:02 UTC174
                        ErrorKind::Invalid,
80381e06a573atagen2026-10-25 03:02 UTC175
                        "no recent history repair",
80381e06a573atagen2026-10-25 03:02 UTC176
                    )));
80381e06a573atagen2026-10-25 03:02 UTC177
                    return;
80381e06a573atagen2026-10-25 03:02 UTC178
                };
80381e06a573atagen2026-10-25 03:02 UTC179
                if !stage.report.recent_available {
80381e06a573atagen2026-10-25 03:02 UTC180
                    self.stage = Some(stage);
80381e06a573atagen2026-10-25 03:02 UTC181
                    reply.done(Err(Error::new(
80381e06a573atagen2026-10-25 03:02 UTC182
                        ErrorKind::Invalid,
80381e06a573atagen2026-10-25 03:02 UTC183
                        "recent history is not available, retry the repair",
80381e06a573atagen2026-10-25 03:02 UTC184
                    )));
80381e06a573atagen2026-10-25 03:02 UTC185
                    return;
80381e06a573atagen2026-10-25 03:02 UTC186
                }
80381e06a573atagen2026-10-25 03:02 UTC187
                stage.report.strategy = HistoryRepairStrategy::Recent;
80381e06a573atagen2026-10-25 03:02 UTC188
                stage.report.phase = HistoryRepairPhase::Ready;
80381e06a573atagen2026-10-25 03:02 UTC189
                stage.report.stop = None;
80381e06a573atagen2026-10-25 03:02 UTC190
                stage.report.failure = None;
80381e06a573atagen2026-10-25 03:02 UTC191
                let report = stage.report.clone();
80381e06a573atagen2026-10-25 03:02 UTC192
                let published = self
80381e06a573atagen2026-10-25 03:02 UTC193
                    .context
80381e06a573atagen2026-10-25 03:02 UTC194
                    .events
80381e06a573atagen2026-10-25 03:02 UTC195
                    .publish_repair(report.clone())
80381e06a573atagen2026-10-25 03:02 UTC196
                    .await
80381e06a573atagen2026-10-25 03:02 UTC197
                    .map_err(|_| Error::interrupted());
80381e06a573atagen2026-10-25 03:02 UTC198
                reply.send(published.map(|_| CommandOutput::HistoryRepair(report)));
80381e06a573atagen2026-10-25 03:02 UTC199
                return;
80381e06a573atagen2026-10-25 03:02 UTC200
            }
80381e06a573atagen2026-10-25 03:02 UTC201
            _ => {
80381e06a573atagen2026-10-25 03:02 UTC202
                reply.done(Err(Error::new(
80381e06a573atagen2026-10-25 03:02 UTC203
                    ErrorKind::Invalid,
80381e06a573atagen2026-10-25 03:02 UTC204
                    "repair command was misrouted",
80381e06a573atagen2026-10-25 03:02 UTC205
                )));
80381e06a573atagen2026-10-25 03:02 UTC206
                return;
80381e06a573atagen2026-10-25 03:02 UTC207
            }
80381e06a573atagen2026-10-25 03:02 UTC208
        };
80381e06a573atagen2026-10-25 03:02 UTC209
        let (cancel, receiver) = watch::channel(false);
80381e06a573atagen2026-10-25 03:02 UTC210
        let room = self.room.clone();
80381e06a573atagen2026-10-25 03:02 UTC211
        let context = self.context.clone();
80381e06a573atagen2026-10-25 03:02 UTC212
        let installs = self.installs.clone();
80381e06a573atagen2026-10-25 03:02 UTC213
        let task =
80381e06a573atagen2026-10-25 03:02 UTC214
            spawn(async move { run(room, context, installs, receiver, stage, budget).await });
80381e06a573atagen2026-10-25 03:02 UTC215
        self.running = Some(Running {
80381e06a573atagen2026-10-25 03:02 UTC216
            task,
80381e06a573atagen2026-10-25 03:02 UTC217
            cancel,
80381e06a573atagen2026-10-25 03:02 UTC218
            reply,
80381e06a573atagen2026-10-25 03:02 UTC219
        });
80381e06a573atagen2026-10-25 03:02 UTC220
    }
80381e06a573atagen2026-10-25 03:02 UTC221
80381e06a573atagen2026-10-25 03:02 UTC222
    pub(super) async fn next_install(&mut self) -> Option<InstallRequest> {
80381e06a573atagen2026-10-25 03:02 UTC223
        let running = self.running.as_mut()?;
80381e06a573atagen2026-10-25 03:02 UTC224
        tokio::select! {
80381e06a573atagen2026-10-25 03:02 UTC225
            request = self.requests.recv() => request,
80381e06a573atagen2026-10-25 03:02 UTC226
            result = &mut running.task => {
80381e06a573atagen2026-10-25 03:02 UTC227
                let running = self.running.take().expect("repair task exists");
80381e06a573atagen2026-10-25 03:02 UTC228
                match result {
80381e06a573atagen2026-10-25 03:02 UTC229
                    Ok(stage) => {
80381e06a573atagen2026-10-25 03:02 UTC230
                        let report = stage.report.clone();
80381e06a573atagen2026-10-25 03:02 UTC231
                        let published = self.context.events.publish_repair(report.clone()).await
80381e06a573atagen2026-10-25 03:02 UTC232
                            .map_err(|_| Error::interrupted());
80381e06a573atagen2026-10-25 03:02 UTC233
                        running.reply.send(published.map(|_| CommandOutput::HistoryRepair(report)));
80381e06a573atagen2026-10-25 03:02 UTC234
                        self.stage = Some(stage);
80381e06a573atagen2026-10-25 03:02 UTC235
                    }
80381e06a573atagen2026-10-25 03:02 UTC236
                    Err(_) => running.reply.done(Err(Error::interrupted())),
80381e06a573atagen2026-10-25 03:02 UTC237
                }
80381e06a573atagen2026-10-25 03:02 UTC238
                None
80381e06a573atagen2026-10-25 03:02 UTC239
            }
80381e06a573atagen2026-10-25 03:02 UTC240
        }
80381e06a573atagen2026-10-25 03:02 UTC241
    }
80381e06a573atagen2026-10-25 03:02 UTC242
80381e06a573atagen2026-10-25 03:02 UTC243
    pub(super) async fn shutdown(&mut self) {
80381e06a573atagen2026-10-25 03:02 UTC244
        if let Some(mut running) = self.running.take() {
80381e06a573atagen2026-10-25 03:02 UTC245
            running.task.abort();
80381e06a573atagen2026-10-25 03:02 UTC246
            match (&mut running.task).await {
80381e06a573atagen2026-10-25 03:02 UTC247
                Ok(stage) => {
80381e06a573atagen2026-10-25 03:02 UTC248
                    let report = stage.report;
80381e06a573atagen2026-10-25 03:02 UTC249
                    let published = self
80381e06a573atagen2026-10-25 03:02 UTC250
                        .context
80381e06a573atagen2026-10-25 03:02 UTC251
                        .events
80381e06a573atagen2026-10-25 03:02 UTC252
                        .publish_repair(report.clone())
80381e06a573atagen2026-10-25 03:02 UTC253
                        .await
80381e06a573atagen2026-10-25 03:02 UTC254
                        .map_err(|_| Error::interrupted());
80381e06a573atagen2026-10-25 03:02 UTC255
                    running
80381e06a573atagen2026-10-25 03:02 UTC256
                        .reply
80381e06a573atagen2026-10-25 03:02 UTC257
                        .send(published.map(|_| CommandOutput::HistoryRepair(report)));
80381e06a573atagen2026-10-25 03:02 UTC258
                }
80381e06a573atagen2026-10-25 03:02 UTC259
                Err(_) => running.reply.done(Err(Error::interrupted())),
80381e06a573atagen2026-10-25 03:02 UTC260
            }
80381e06a573atagen2026-10-25 03:02 UTC261
        }
80381e06a573atagen2026-10-25 03:02 UTC262
    }
80381e06a573atagen2026-10-25 03:02 UTC263
}
80381e06a573atagen2026-10-25 03:02 UTC264
80381e06a573atagen2026-10-25 03:02 UTC265
impl Drop for RepairController {
80381e06a573atagen2026-10-25 03:02 UTC266
    fn drop(&mut self) {
80381e06a573atagen2026-10-25 03:02 UTC267
        if let Some(running) = &self.running {
80381e06a573atagen2026-10-25 03:02 UTC268
            running.task.abort();
80381e06a573atagen2026-10-25 03:02 UTC269
        }
80381e06a573atagen2026-10-25 03:02 UTC270
    }
80381e06a573atagen2026-10-25 03:02 UTC271
}
80381e06a573atagen2026-10-25 03:02 UTC272
80381e06a573atagen2026-10-25 03:02 UTC273
fn validate_budget(budget: &HistoryRepairBudget) -> Result<(), Error> {
80381e06a573atagen2026-10-25 03:02 UTC274
    if !(1..=100).contains(&budget.max_pages)
80381e06a573atagen2026-10-25 03:02 UTC275
        || !(50..=10_000).contains(&budget.max_events)
80381e06a573atagen2026-10-25 03:02 UTC276
        || !(1..=60_000).contains(&budget.max_duration_ms)
80381e06a573atagen2026-10-25 03:02 UTC277
    {
80381e06a573atagen2026-10-25 03:02 UTC278
        return Err(Error::new(
80381e06a573atagen2026-10-25 03:02 UTC279
            ErrorKind::Invalid,
80381e06a573atagen2026-10-25 03:02 UTC280
            "repair budget requires 1 to 100 pages, 50 to 10000 events, and 1 to 60000 milliseconds",
80381e06a573atagen2026-10-25 03:02 UTC281
        ));
80381e06a573atagen2026-10-25 03:02 UTC282
    }
80381e06a573atagen2026-10-25 03:02 UTC283
    Ok(())
80381e06a573atagen2026-10-25 03:02 UTC284
}
80381e06a573atagen2026-10-25 03:02 UTC285
80381e06a573atagen2026-10-25 03:02 UTC286
fn should_bridge(policy: &HistoryRepairPolicy, duration: Option<u64>, boundary: bool) -> bool {
80381e06a573atagen2026-10-25 03:02 UTC287
    match policy {
80381e06a573atagen2026-10-25 03:02 UTC288
        HistoryRepairPolicy::Bridge => true,
80381e06a573atagen2026-10-25 03:02 UTC289
        HistoryRepairPolicy::StartRecent => false,
80381e06a573atagen2026-10-25 03:02 UTC290
        HistoryRepairPolicy::BridgeShortGaps { threshold_ms } => {
80381e06a573atagen2026-10-25 03:02 UTC291
            boundary
80381e06a573atagen2026-10-25 03:02 UTC292
                && *threshold_ms > 0
80381e06a573atagen2026-10-25 03:02 UTC293
                && duration.is_some_and(|duration| duration <= *threshold_ms)
80381e06a573atagen2026-10-25 03:02 UTC294
        }
80381e06a573atagen2026-10-25 03:02 UTC295
    }
80381e06a573atagen2026-10-25 03:02 UTC296
}
80381e06a573atagen2026-10-25 03:02 UTC297
80381e06a573atagen2026-10-25 03:02 UTC298
async fn run(
80381e06a573atagen2026-10-25 03:02 UTC299
    room: Room,
80381e06a573atagen2026-10-25 03:02 UTC300
    context: SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC301
    installs: mpsc::Sender<InstallRequest>,
80381e06a573atagen2026-10-25 03:02 UTC302
    mut cancel: watch::Receiver<bool>,
80381e06a573atagen2026-10-25 03:02 UTC303
    mut stage: Stage,
80381e06a573atagen2026-10-25 03:02 UTC304
    budget: HistoryRepairBudget,
80381e06a573atagen2026-10-25 03:02 UTC305
) -> Stage {
80381e06a573atagen2026-10-25 03:02 UTC306
    let started = Instant::now();
80381e06a573atagen2026-10-25 03:02 UTC307
    let initial_pages = stage.report.pages;
80381e06a573atagen2026-10-25 03:02 UTC308
    let initial_events = stage.report.events;
80381e06a573atagen2026-10-25 03:02 UTC309
    let result = async {
80381e06a573atagen2026-10-25 03:02 UTC310
        if !stage.report.recent_available {
80381e06a573atagen2026-10-25 03:02 UTC311
            let (cache, _handles) = room.event_cache().await.map_err(cache_error)?;
80381e06a573atagen2026-10-25 03:02 UTC312
            stage.anchor = cache
80381e06a573atagen2026-10-25 03:02 UTC313
                .events()
80381e06a573atagen2026-10-25 03:02 UTC314
                .await
80381e06a573atagen2026-10-25 03:02 UTC315
                .map_err(cache_error)?
80381e06a573atagen2026-10-25 03:02 UTC316
                .iter()
80381e06a573atagen2026-10-25 03:02 UTC317
                .find_map(TimelineEvent::event_id);
80381e06a573atagen2026-10-25 03:02 UTC318
            let _ = context
80381e06a573atagen2026-10-25 03:02 UTC319
                .events
80381e06a573atagen2026-10-25 03:02 UTC320
                .send(Event::HistoryRepairChanged(stage.report.clone()))
80381e06a573atagen2026-10-25 03:02 UTC321
                .await;
80381e06a573atagen2026-10-25 03:02 UTC322
            let guard = pause_sync(&context, &mut cancel, &started, &budget).await?;
80381e06a573atagen2026-10-25 03:02 UTC323
            let page = fetch(&room, None, &mut cancel, &started, &budget).await?;
80381e06a573atagen2026-10-25 03:02 UTC324
            stage.report.pages += 1;
80381e06a573atagen2026-10-25 03:02 UTC325
            stage.report.events += page.chunk.len() as u32;
80381e06a573atagen2026-10-25 03:02 UTC326
            stage.cursor = page.end;
80381e06a573atagen2026-10-25 03:02 UTC327
            stage.events = page.chunk.into_iter().rev().collect();
80381e06a573atagen2026-10-25 03:02 UTC328
            deduplicate(&mut stage.events);
80381e06a573atagen2026-10-25 03:02 UTC329
            install(&installs, &stage).await?;
80381e06a573atagen2026-10-25 03:02 UTC330
            stage.report.recent_available = true;
80381e06a573atagen2026-10-25 03:02 UTC331
            if contains_anchor(&stage) {
80381e06a573atagen2026-10-25 03:02 UTC332
                stage.report.strategy = HistoryRepairStrategy::Overlap;
80381e06a573atagen2026-10-25 03:02 UTC333
                stage.report.continuity = HistoryContinuity::Verified;
80381e06a573atagen2026-10-25 03:02 UTC334
            } else if stage.cursor.is_none() {
80381e06a573atagen2026-10-25 03:02 UTC335
                stage.report.continuity = HistoryContinuity::AccessibleStart;
80381e06a573atagen2026-10-25 03:02 UTC336
            }
80381e06a573atagen2026-10-25 03:02 UTC337
            if stage.report.continuity != HistoryContinuity::Unresolved {
80381e06a573atagen2026-10-25 03:02 UTC338
                context
80381e06a573atagen2026-10-25 03:02 UTC339
                    .mentions
80381e06a573atagen2026-10-25 03:02 UTC340
                    .repair_resolved(room.room_id().to_owned())
80381e06a573atagen2026-10-25 03:02 UTC341
                    .await?;
80381e06a573atagen2026-10-25 03:02 UTC342
            }
80381e06a573atagen2026-10-25 03:02 UTC343
            guard.finish().await;
80381e06a573atagen2026-10-25 03:02 UTC344
            let _ = context
80381e06a573atagen2026-10-25 03:02 UTC345
                .events
80381e06a573atagen2026-10-25 03:02 UTC346
                .send(Event::HistoryRepairChanged(stage.report.clone()))
80381e06a573atagen2026-10-25 03:02 UTC347
                .await;
80381e06a573atagen2026-10-25 03:02 UTC348
            if stage.report.continuity != HistoryContinuity::Unresolved {
80381e06a573atagen2026-10-25 03:02 UTC349
                return Ok(());
80381e06a573atagen2026-10-25 03:02 UTC350
            }
80381e06a573atagen2026-10-25 03:02 UTC351
            if !should_bridge(
80381e06a573atagen2026-10-25 03:02 UTC352
                &stage.report.requested_policy,
80381e06a573atagen2026-10-25 03:02 UTC353
                stage.report.coverage_duration_ms,
80381e06a573atagen2026-10-25 03:02 UTC354
                stage.report.threshold_evaluated,
80381e06a573atagen2026-10-25 03:02 UTC355
            ) {
80381e06a573atagen2026-10-25 03:02 UTC356
                return Ok(());
80381e06a573atagen2026-10-25 03:02 UTC357
            }
80381e06a573atagen2026-10-25 03:02 UTC358
            stage.report.strategy = HistoryRepairStrategy::Bridge;
80381e06a573atagen2026-10-25 03:02 UTC359
        }
80381e06a573atagen2026-10-25 03:02 UTC360
        stage.report.phase = HistoryRepairPhase::Bridging;
80381e06a573atagen2026-10-25 03:02 UTC361
        let _ = context
80381e06a573atagen2026-10-25 03:02 UTC362
            .events
80381e06a573atagen2026-10-25 03:02 UTC363
            .send(Event::HistoryRepairChanged(stage.report.clone()))
80381e06a573atagen2026-10-25 03:02 UTC364
            .await;
80381e06a573atagen2026-10-25 03:02 UTC365
        if contains_anchor(&stage) {
80381e06a573atagen2026-10-25 03:02 UTC366
            stage.report.continuity = HistoryContinuity::Verified;
80381e06a573atagen2026-10-25 03:02 UTC367
        } else if stage.cursor.is_none() {
80381e06a573atagen2026-10-25 03:02 UTC368
            stage.report.continuity = HistoryContinuity::AccessibleStart;
80381e06a573atagen2026-10-25 03:02 UTC369
        }
80381e06a573atagen2026-10-25 03:02 UTC370
        while stage.report.continuity == HistoryContinuity::Unresolved {
80381e06a573atagen2026-10-25 03:02 UTC371
            if *cancel.borrow() {
80381e06a573atagen2026-10-25 03:02 UTC372
                stage.report.stop = Some(HistoryRepairStop::Cancelled);
80381e06a573atagen2026-10-25 03:02 UTC373
                break;
80381e06a573atagen2026-10-25 03:02 UTC374
            }
80381e06a573atagen2026-10-25 03:02 UTC375
            if stage.report.pages - initial_pages + 1 >= u32::from(budget.max_pages)
80381e06a573atagen2026-10-25 03:02 UTC376
                || stage.report.events - initial_events + 2 * u32::from(PAGE_SIZE)
80381e06a573atagen2026-10-25 03:02 UTC377
                    > budget.max_events
80381e06a573atagen2026-10-25 03:02 UTC378
                || stage.events.len() + 2 * usize::from(PAGE_SIZE) > STAGING_LIMIT
80381e06a573atagen2026-10-25 03:02 UTC379
                || started.elapsed() >= Duration::from_millis(budget.max_duration_ms)
80381e06a573atagen2026-10-25 03:02 UTC380
            {
80381e06a573atagen2026-10-25 03:02 UTC381
                stage.report.stop = Some(HistoryRepairStop::Budget);
80381e06a573atagen2026-10-25 03:02 UTC382
                break;
80381e06a573atagen2026-10-25 03:02 UTC383
            }
80381e06a573atagen2026-10-25 03:02 UTC384
            if stage.anchor.is_none() {
80381e06a573atagen2026-10-25 03:02 UTC385
                stage.report.stop = Some(HistoryRepairStop::NoAnchor);
80381e06a573atagen2026-10-25 03:02 UTC386
                break;
80381e06a573atagen2026-10-25 03:02 UTC387
            }
80381e06a573atagen2026-10-25 03:02 UTC388
            let Some(cursor) = stage.cursor.clone() else {
80381e06a573atagen2026-10-25 03:02 UTC389
                break;
80381e06a573atagen2026-10-25 03:02 UTC390
            };
80381e06a573atagen2026-10-25 03:02 UTC391
            let page = fetch(&room, Some(&cursor), &mut cancel, &started, &budget).await?;
80381e06a573atagen2026-10-25 03:02 UTC392
            stage.report.pages += 1;
80381e06a573atagen2026-10-25 03:02 UTC393
            stage.report.events += page.chunk.len() as u32;
80381e06a573atagen2026-10-25 03:02 UTC394
            if page.end.as_deref() == Some(&cursor) {
80381e06a573atagen2026-10-25 03:02 UTC395
                stage.report.stop = Some(HistoryRepairStop::CursorStalled);
80381e06a573atagen2026-10-25 03:02 UTC396
                break;
80381e06a573atagen2026-10-25 03:02 UTC397
            }
80381e06a573atagen2026-10-25 03:02 UTC398
            let mut older: Vec<_> = page.chunk.into_iter().rev().collect();
80381e06a573atagen2026-10-25 03:02 UTC399
            older.append(&mut stage.events);
80381e06a573atagen2026-10-25 03:02 UTC400
            deduplicate(&mut older);
80381e06a573atagen2026-10-25 03:02 UTC401
            stage.events = older;
80381e06a573atagen2026-10-25 03:02 UTC402
            stage.cursor = page.end;
80381e06a573atagen2026-10-25 03:02 UTC403
            if contains_anchor(&stage) {
80381e06a573atagen2026-10-25 03:02 UTC404
                stage.report.continuity = HistoryContinuity::Verified;
80381e06a573atagen2026-10-25 03:02 UTC405
            } else if stage.cursor.is_none() {
80381e06a573atagen2026-10-25 03:02 UTC406
                stage.report.continuity = HistoryContinuity::AccessibleStart;
80381e06a573atagen2026-10-25 03:02 UTC407
            }
80381e06a573atagen2026-10-25 03:02 UTC408
            if stage.report.continuity == HistoryContinuity::Unresolved {
80381e06a573atagen2026-10-25 03:02 UTC409
                let _ = context
80381e06a573atagen2026-10-25 03:02 UTC410
                    .events
80381e06a573atagen2026-10-25 03:02 UTC411
                    .send(Event::HistoryRepairChanged(stage.report.clone()))
80381e06a573atagen2026-10-25 03:02 UTC412
                    .await;
80381e06a573atagen2026-10-25 03:02 UTC413
            }
80381e06a573atagen2026-10-25 03:02 UTC414
        }
80381e06a573atagen2026-10-25 03:02 UTC415
        if stage.report.continuity != HistoryContinuity::Unresolved
80381e06a573atagen2026-10-25 03:02 UTC416
            && stage.report.strategy == HistoryRepairStrategy::Bridge
80381e06a573atagen2026-10-25 03:02 UTC417
        {
80381e06a573atagen2026-10-25 03:02 UTC418
            let guard = pause_sync(&context, &mut cancel, &started, &budget).await?;
80381e06a573atagen2026-10-25 03:02 UTC419
            let head = fetch(&room, None, &mut cancel, &started, &budget).await?;
80381e06a573atagen2026-10-25 03:02 UTC420
            stage.report.pages += 1;
80381e06a573atagen2026-10-25 03:02 UTC421
            stage.report.events += head.chunk.len() as u32;
80381e06a573atagen2026-10-25 03:02 UTC422
            let head: Vec<_> = head.chunk.into_iter().rev().collect();
80381e06a573atagen2026-10-25 03:02 UTC423
            if !extend_head(&mut stage.events, head) {
80381e06a573atagen2026-10-25 03:02 UTC424
                stage.report.continuity = HistoryContinuity::Unresolved;
80381e06a573atagen2026-10-25 03:02 UTC425
                stage.report.stop = Some(HistoryRepairStop::HistoryUnavailable);
80381e06a573atagen2026-10-25 03:02 UTC426
                return Ok(());
80381e06a573atagen2026-10-25 03:02 UTC427
            }
80381e06a573atagen2026-10-25 03:02 UTC428
            install(&installs, &stage).await?;
80381e06a573atagen2026-10-25 03:02 UTC429
            context
80381e06a573atagen2026-10-25 03:02 UTC430
                .mentions
80381e06a573atagen2026-10-25 03:02 UTC431
                .repair_resolved(room.room_id().to_owned())
80381e06a573atagen2026-10-25 03:02 UTC432
                .await?;
80381e06a573atagen2026-10-25 03:02 UTC433
            guard.finish().await;
80381e06a573atagen2026-10-25 03:02 UTC434
        }
80381e06a573atagen2026-10-25 03:02 UTC435
        Ok::<_, Error>(())
80381e06a573atagen2026-10-25 03:02 UTC436
    }
80381e06a573atagen2026-10-25 03:02 UTC437
    .await;
80381e06a573atagen2026-10-25 03:02 UTC438
    if let Err(error) = result {
80381e06a573atagen2026-10-25 03:02 UTC439
        if error.kind == ErrorKind::Interrupted {
80381e06a573atagen2026-10-25 03:02 UTC440
            stage.report.stop = Some(HistoryRepairStop::Cancelled);
80381e06a573atagen2026-10-25 03:02 UTC441
        } else if error.kind == ErrorKind::RoomBusy {
80381e06a573atagen2026-10-25 03:02 UTC442
            stage.report.stop = Some(HistoryRepairStop::Budget);
80381e06a573atagen2026-10-25 03:02 UTC443
        } else {
80381e06a573atagen2026-10-25 03:02 UTC444
            stage.report.failure = Some(error);
80381e06a573atagen2026-10-25 03:02 UTC445
        }
80381e06a573atagen2026-10-25 03:02 UTC446
        stage.report.continuity = HistoryContinuity::Unresolved;
80381e06a573atagen2026-10-25 03:02 UTC447
    }
80381e06a573atagen2026-10-25 03:02 UTC448
    stage.report.phase = if stage.report.stop.is_some() || stage.report.failure.is_some() {
80381e06a573atagen2026-10-25 03:02 UTC449
        HistoryRepairPhase::Paused
80381e06a573atagen2026-10-25 03:02 UTC450
    } else {
80381e06a573atagen2026-10-25 03:02 UTC451
        HistoryRepairPhase::Ready
80381e06a573atagen2026-10-25 03:02 UTC452
    };
80381e06a573atagen2026-10-25 03:02 UTC453
    stage
80381e06a573atagen2026-10-25 03:02 UTC454
}
80381e06a573atagen2026-10-25 03:02 UTC455
80381e06a573atagen2026-10-25 03:02 UTC456
fn contains_anchor(stage: &Stage) -> bool {
80381e06a573atagen2026-10-25 03:02 UTC457
    stage.anchor.as_ref().is_some_and(|anchor| {
80381e06a573atagen2026-10-25 03:02 UTC458
        stage
80381e06a573atagen2026-10-25 03:02 UTC459
            .events
80381e06a573atagen2026-10-25 03:02 UTC460
            .iter()
80381e06a573atagen2026-10-25 03:02 UTC461
            .any(|event| event.event_id().as_ref() == Some(anchor))
80381e06a573atagen2026-10-25 03:02 UTC462
    })
80381e06a573atagen2026-10-25 03:02 UTC463
}
80381e06a573atagen2026-10-25 03:02 UTC464
80381e06a573atagen2026-10-25 03:02 UTC465
fn deduplicate(events: &mut Vec<TimelineEvent>) {
80381e06a573atagen2026-10-25 03:02 UTC466
    let mut seen = HashSet::new();
80381e06a573atagen2026-10-25 03:02 UTC467
    events.retain(|event| event.event_id().is_some_and(|id| seen.insert(id)));
80381e06a573atagen2026-10-25 03:02 UTC468
}
80381e06a573atagen2026-10-25 03:02 UTC469
80381e06a573atagen2026-10-25 03:02 UTC470
fn extend_head(events: &mut Vec<TimelineEvent>, head: Vec<TimelineEvent>) -> bool {
80381e06a573atagen2026-10-25 03:02 UTC471
    let ids: HashSet<_> = events.iter().filter_map(TimelineEvent::event_id).collect();
80381e06a573atagen2026-10-25 03:02 UTC472
    let Some(overlap) = head
80381e06a573atagen2026-10-25 03:02 UTC473
        .iter()
80381e06a573atagen2026-10-25 03:02 UTC474
        .position(|event| event.event_id().is_some_and(|id| ids.contains(&id)))
80381e06a573atagen2026-10-25 03:02 UTC475
    else {
80381e06a573atagen2026-10-25 03:02 UTC476
        return events.is_empty() && head.is_empty();
80381e06a573atagen2026-10-25 03:02 UTC477
    };
80381e06a573atagen2026-10-25 03:02 UTC478
    events.extend(head.into_iter().skip(overlap + 1));
80381e06a573atagen2026-10-25 03:02 UTC479
    deduplicate(events);
80381e06a573atagen2026-10-25 03:02 UTC480
    true
80381e06a573atagen2026-10-25 03:02 UTC481
}
80381e06a573atagen2026-10-25 03:02 UTC482
80381e06a573atagen2026-10-25 03:02 UTC483
async fn install(installs: &mpsc::Sender<InstallRequest>, stage: &Stage) -> Result<(), Error> {
80381e06a573atagen2026-10-25 03:02 UTC484
    let (reply, response) = oneshot::channel();
80381e06a573atagen2026-10-25 03:02 UTC485
    installs
80381e06a573atagen2026-10-25 03:02 UTC486
        .send(InstallRequest {
80381e06a573atagen2026-10-25 03:02 UTC487
            events: stage.events.clone(),
80381e06a573atagen2026-10-25 03:02 UTC488
            prev_batch: stage.cursor.clone(),
80381e06a573atagen2026-10-25 03:02 UTC489
            reply,
80381e06a573atagen2026-10-25 03:02 UTC490
        })
80381e06a573atagen2026-10-25 03:02 UTC491
        .await
80381e06a573atagen2026-10-25 03:02 UTC492
        .map_err(|_| Error::interrupted())?;
80381e06a573atagen2026-10-25 03:02 UTC493
    response.await.map_err(|_| Error::interrupted())?
80381e06a573atagen2026-10-25 03:02 UTC494
}
80381e06a573atagen2026-10-25 03:02 UTC495
80381e06a573atagen2026-10-25 03:02 UTC496
async fn fetch(
80381e06a573atagen2026-10-25 03:02 UTC497
    room: &Room,
80381e06a573atagen2026-10-25 03:02 UTC498
    from: Option<&str>,
80381e06a573atagen2026-10-25 03:02 UTC499
    cancel: &mut watch::Receiver<bool>,
80381e06a573atagen2026-10-25 03:02 UTC500
    started: &Instant,
80381e06a573atagen2026-10-25 03:02 UTC501
    budget: &HistoryRepairBudget,
80381e06a573atagen2026-10-25 03:02 UTC502
) -> Result<matrix_sdk::room::Messages, Error> {
80381e06a573atagen2026-10-25 03:02 UTC503
    let remaining = Duration::from_millis(budget.max_duration_ms).saturating_sub(started.elapsed());
80381e06a573atagen2026-10-25 03:02 UTC504
    if *cancel.borrow() {
80381e06a573atagen2026-10-25 03:02 UTC505
        return Err(Error::interrupted());
80381e06a573atagen2026-10-25 03:02 UTC506
    }
80381e06a573atagen2026-10-25 03:02 UTC507
    let mut options = MessagesOptions::backward().from(from);
80381e06a573atagen2026-10-25 03:02 UTC508
    options.limit = PAGE_SIZE.into();
80381e06a573atagen2026-10-25 03:02 UTC509
    tokio::select! {
80381e06a573atagen2026-10-25 03:02 UTC510
        _ = cancel.changed() => Err(Error::interrupted()),
80381e06a573atagen2026-10-25 03:02 UTC511
        result = matrix_sdk::timeout::timeout(room.messages(options), remaining) => {
80381e06a573atagen2026-10-25 03:02 UTC512
            let page = result.map_err(|_| Error::new(ErrorKind::RoomBusy, "repair work duration exhausted"))?
80381e06a573atagen2026-10-25 03:02 UTC513
                .map_err(|source| Error::from_sdk("fetching repair history failed", source))?;
80381e06a573atagen2026-10-25 03:02 UTC514
            if page.chunk.len() > usize::from(PAGE_SIZE) {
80381e06a573atagen2026-10-25 03:02 UTC515
                return Err(Error::new(ErrorKind::Invalid, "server exceeded the repair page limit"));
80381e06a573atagen2026-10-25 03:02 UTC516
            }
80381e06a573atagen2026-10-25 03:02 UTC517
            Ok(page)
80381e06a573atagen2026-10-25 03:02 UTC518
        }
80381e06a573atagen2026-10-25 03:02 UTC519
    }
80381e06a573atagen2026-10-25 03:02 UTC520
}
80381e06a573atagen2026-10-25 03:02 UTC521
80381e06a573atagen2026-10-25 03:02 UTC522
struct SyncPause {
80381e06a573atagen2026-10-25 03:02 UTC523
    sync: Arc<SyncService>,
80381e06a573atagen2026-10-25 03:02 UTC524
    shutdown: Arc<AtomicBool>,
80381e06a573atagen2026-10-25 03:02 UTC525
    gate: Option<OwnedMutexGuard<()>>,
80381e06a573atagen2026-10-25 03:02 UTC526
}
80381e06a573atagen2026-10-25 03:02 UTC527
80381e06a573atagen2026-10-25 03:02 UTC528
impl SyncPause {
80381e06a573atagen2026-10-25 03:02 UTC529
    async fn finish(mut self) {
80381e06a573atagen2026-10-25 03:02 UTC530
        if !self.shutdown.load(Ordering::Acquire) {
80381e06a573atagen2026-10-25 03:02 UTC531
            self.sync.start().await;
80381e06a573atagen2026-10-25 03:02 UTC532
        }
80381e06a573atagen2026-10-25 03:02 UTC533
        self.gate.take();
80381e06a573atagen2026-10-25 03:02 UTC534
    }
80381e06a573atagen2026-10-25 03:02 UTC535
}
80381e06a573atagen2026-10-25 03:02 UTC536
80381e06a573atagen2026-10-25 03:02 UTC537
impl Drop for SyncPause {
80381e06a573atagen2026-10-25 03:02 UTC538
    fn drop(&mut self) {
80381e06a573atagen2026-10-25 03:02 UTC539
        if let Some(gate) = self.gate.take() {
80381e06a573atagen2026-10-25 03:02 UTC540
            let sync = self.sync.clone();
80381e06a573atagen2026-10-25 03:02 UTC541
            let shutdown = self.shutdown.clone();
80381e06a573atagen2026-10-25 03:02 UTC542
            spawn(async move {
80381e06a573atagen2026-10-25 03:02 UTC543
                if !shutdown.load(Ordering::Acquire) {
80381e06a573atagen2026-10-25 03:02 UTC544
                    sync.start().await;
80381e06a573atagen2026-10-25 03:02 UTC545
                }
80381e06a573atagen2026-10-25 03:02 UTC546
                drop(gate);
80381e06a573atagen2026-10-25 03:02 UTC547
            });
80381e06a573atagen2026-10-25 03:02 UTC548
        }
80381e06a573atagen2026-10-25 03:02 UTC549
    }
80381e06a573atagen2026-10-25 03:02 UTC550
}
80381e06a573atagen2026-10-25 03:02 UTC551
80381e06a573atagen2026-10-25 03:02 UTC552
async fn pause_sync(
80381e06a573atagen2026-10-25 03:02 UTC553
    context: &SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC554
    cancel: &mut watch::Receiver<bool>,
80381e06a573atagen2026-10-25 03:02 UTC555
    started: &Instant,
80381e06a573atagen2026-10-25 03:02 UTC556
    budget: &HistoryRepairBudget,
80381e06a573atagen2026-10-25 03:02 UTC557
) -> Result<SyncPause, Error> {
80381e06a573atagen2026-10-25 03:02 UTC558
    let remaining = Duration::from_millis(budget.max_duration_ms).saturating_sub(started.elapsed());
80381e06a573atagen2026-10-25 03:02 UTC559
    let gate = tokio::select! {
80381e06a573atagen2026-10-25 03:02 UTC560
        _ = cancel.changed() => return Err(Error::interrupted()),
80381e06a573atagen2026-10-25 03:02 UTC561
        result = matrix_sdk::timeout::timeout(context.repair_gate.clone().lock_owned(), remaining) =>
80381e06a573atagen2026-10-25 03:02 UTC562
            result.map_err(|_| Error::new(ErrorKind::RoomBusy, "repair work duration exhausted"))?,
80381e06a573atagen2026-10-25 03:02 UTC563
    };
80381e06a573atagen2026-10-25 03:02 UTC564
    if context.repair_shutdown.load(Ordering::Acquire) {
80381e06a573atagen2026-10-25 03:02 UTC565
        return Err(Error::interrupted());
80381e06a573atagen2026-10-25 03:02 UTC566
    }
80381e06a573atagen2026-10-25 03:02 UTC567
    let guard = SyncPause {
80381e06a573atagen2026-10-25 03:02 UTC568
        sync: context.sync.clone(),
80381e06a573atagen2026-10-25 03:02 UTC569
        shutdown: context.repair_shutdown.clone(),
80381e06a573atagen2026-10-25 03:02 UTC570
        gate: Some(gate),
80381e06a573atagen2026-10-25 03:02 UTC571
    };
80381e06a573atagen2026-10-25 03:02 UTC572
    context.sync.stop().await;
80381e06a573atagen2026-10-25 03:02 UTC573
    Ok(guard)
80381e06a573atagen2026-10-25 03:02 UTC574
}
80381e06a573atagen2026-10-25 03:02 UTC575
80381e06a573atagen2026-10-25 03:02 UTC576
fn cache_error(source: matrix_sdk::event_cache::EventCacheError) -> Error {
80381e06a573atagen2026-10-25 03:02 UTC577
    Error::with_source(
80381e06a573atagen2026-10-25 03:02 UTC578
        ErrorKind::Store,
80381e06a573atagen2026-10-25 03:02 UTC579
        "room history cache operation failed",
80381e06a573atagen2026-10-25 03:02 UTC580
        source,
80381e06a573atagen2026-10-25 03:02 UTC581
    )
80381e06a573atagen2026-10-25 03:02 UTC582
}
80381e06a573atagen2026-10-25 03:02 UTC583
80381e06a573atagen2026-10-25 03:02 UTC584
#[cfg(test)]
80381e06a573atagen2026-10-25 03:02 UTC585
mod tests {
80381e06a573atagen2026-10-25 03:02 UTC586
    use super::*;
80381e06a573atagen2026-10-25 03:02 UTC587
80381e06a573atagen2026-10-25 03:02 UTC588
    #[test]
80381e06a573atagen2026-10-25 03:02 UTC589
    fn threshold_requires_known_coverage_and_an_independent_boundary() {
80381e06a573atagen2026-10-25 03:02 UTC590
        let policy = HistoryRepairPolicy::BridgeShortGaps { threshold_ms: 100 };
80381e06a573atagen2026-10-25 03:02 UTC591
        assert!(should_bridge(&policy, Some(100), true));
80381e06a573atagen2026-10-25 03:02 UTC592
        assert!(should_bridge(&policy, Some(99), true));
80381e06a573atagen2026-10-25 03:02 UTC593
        assert!(!should_bridge(&policy, Some(101), true));
80381e06a573atagen2026-10-25 03:02 UTC594
        assert!(!should_bridge(&policy, None, true));
80381e06a573atagen2026-10-25 03:02 UTC595
        assert!(!should_bridge(&policy, Some(1), false));
80381e06a573atagen2026-10-25 03:02 UTC596
        assert!(!should_bridge(
80381e06a573atagen2026-10-25 03:02 UTC597
            &HistoryRepairPolicy::BridgeShortGaps { threshold_ms: 0 },
80381e06a573atagen2026-10-25 03:02 UTC598
            Some(0),
80381e06a573atagen2026-10-25 03:02 UTC599
            true
80381e06a573atagen2026-10-25 03:02 UTC600
        ));
80381e06a573atagen2026-10-25 03:02 UTC601
        assert!(should_bridge(&HistoryRepairPolicy::Bridge, None, false));
80381e06a573atagen2026-10-25 03:02 UTC602
        assert!(!should_bridge(
80381e06a573atagen2026-10-25 03:02 UTC603
            &HistoryRepairPolicy::StartRecent,
80381e06a573atagen2026-10-25 03:02 UTC604
            Some(1),
80381e06a573atagen2026-10-25 03:02 UTC605
            true
80381e06a573atagen2026-10-25 03:02 UTC606
        ));
80381e06a573atagen2026-10-25 03:02 UTC607
    }
80381e06a573atagen2026-10-25 03:02 UTC608
}