atagen

macltok

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

Blame: src/engine/repair.rs

d9b96952ec60atagen2026-10-25 09:07 UTC1
use matrix_sdk::{
d9b96952ec60atagen2026-10-25 09:07 UTC2
    Room,
d9b96952ec60atagen2026-10-25 09:07 UTC3
    event_cache::{EventCacheError, HistoryGapFillOutcome, HistoryGapStatus},
d9b96952ec60atagen2026-10-25 09:07 UTC4
};
d9b96952ec60atagen2026-10-25 09:07 UTC5
use ruma::{OwnedRoomId, OwnedTransactionId};
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC6
use tokio::sync::watch;
80381e06a573atagen2026-10-25 03:02 UTC7
80381e06a573atagen2026-10-25 03:02 UTC8
use super::{
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC9
    executor::{JoinError, JoinHandle, spawn},
80381e06a573atagen2026-10-25 03:02 UTC10
    timeline::SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC11
};
80381e06a573atagen2026-10-25 03:02 UTC12
use crate::{
d9b96952ec60atagen2026-10-25 09:07 UTC13
    CommandOutput, Error, ErrorKind, Event, HistoryContinuity, HistoryRepairCommand,
d9b96952ec60atagen2026-10-25 09:07 UTC14
    HistoryRepairPhase, HistoryRepairReport, HistoryRepairStop, RecentHistoryState,
d9b96952ec60atagen2026-10-25 09:07 UTC15
    transport::Responder,
80381e06a573atagen2026-10-25 03:02 UTC16
};
80381e06a573atagen2026-10-25 03:02 UTC17
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC18
const MAX_GAP_PAGES: u32 = 4;
80381e06a573atagen2026-10-25 03:02 UTC19
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC20
struct Running {
d9b96952ec60atagen2026-10-25 09:07 UTC21
    task: JoinHandle<Finished>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC22
    cancel: watch::Sender<bool>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC23
    responders: Vec<PendingResponder>,
d9b96952ec60atagen2026-10-25 09:07 UTC24
    gap_id: Option<OwnedTransactionId>,
d9b96952ec60atagen2026-10-25 09:07 UTC25
    closing: bool,
80381e06a573atagen2026-10-25 03:02 UTC26
}
80381e06a573atagen2026-10-25 03:02 UTC27
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC28
struct PendingResponder {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC29
    responder: Responder,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC30
    cancel_ack: bool,
80381e06a573atagen2026-10-25 03:02 UTC31
}
80381e06a573atagen2026-10-25 03:02 UTC32
d9b96952ec60atagen2026-10-25 09:07 UTC33
pub(super) enum Outcome {
d9b96952ec60atagen2026-10-25 09:07 UTC34
    Recent(RecentHistoryState),
d9b96952ec60atagen2026-10-25 09:07 UTC35
    Fill(HistoryRepairReport),
d9b96952ec60atagen2026-10-25 09:07 UTC36
}
d9b96952ec60atagen2026-10-25 09:07 UTC37
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC38
pub(super) struct Completion {
d9b96952ec60atagen2026-10-25 09:07 UTC39
    pub(super) outcome: Outcome,
d9b96952ec60atagen2026-10-25 09:07 UTC40
    /// history went into the cache, so the room timeline has to show it before anyone hears
d9b96952ec60atagen2026-10-25 09:07 UTC41
    pub(super) committed: bool,
d9b96952ec60atagen2026-10-25 09:07 UTC42
    pub(super) closing: bool,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC43
    pub(super) result: Result<CommandOutput, Error>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC44
    responders: Vec<PendingResponder>,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC45
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC46
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC47
impl Completion {
d9b96952ec60atagen2026-10-25 09:07 UTC48
    /// the room timeline couldn't show what was committed
d9b96952ec60atagen2026-10-25 09:07 UTC49
    pub(super) fn unpublished(&mut self, error: Error) {
d9b96952ec60atagen2026-10-25 09:07 UTC50
        match &mut self.outcome {
d9b96952ec60atagen2026-10-25 09:07 UTC51
            Outcome::Recent(state) => *state = RecentHistoryState::Failed(error.message.clone()),
d9b96952ec60atagen2026-10-25 09:07 UTC52
            Outcome::Fill(report) => {
d9b96952ec60atagen2026-10-25 09:07 UTC53
                report.phase = HistoryRepairPhase::Paused;
d9b96952ec60atagen2026-10-25 09:07 UTC54
                report.failure = Some(error.clone());
d9b96952ec60atagen2026-10-25 09:07 UTC55
            }
d9b96952ec60atagen2026-10-25 09:07 UTC56
        }
d9b96952ec60atagen2026-10-25 09:07 UTC57
        self.result = Err(error);
d9b96952ec60atagen2026-10-25 09:07 UTC58
    }
d9b96952ec60atagen2026-10-25 09:07 UTC59
9eabb6b7587datagen2026-10-25 07:08 UTC60
    pub(super) fn resolve(
9eabb6b7587datagen2026-10-25 07:08 UTC61
        self,
9eabb6b7587datagen2026-10-25 07:08 UTC62
        result: Result<CommandOutput, Error>,
9eabb6b7587datagen2026-10-25 07:08 UTC63
        cancellation: Result<(), Error>,
9eabb6b7587datagen2026-10-25 07:08 UTC64
    ) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC65
        for responder in self.responders {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC66
            if responder.cancel_ack {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC67
                responder.responder.done(cancellation.clone());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC68
            } else {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC69
                responder.responder.send(result.clone());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC70
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC71
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC72
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC73
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC74
d9b96952ec60atagen2026-10-25 09:07 UTC75
enum Stopped {
d9b96952ec60atagen2026-10-25 09:07 UTC76
    Done,
d9b96952ec60atagen2026-10-25 09:07 UTC77
    Cancelled,
d9b96952ec60atagen2026-10-25 09:07 UTC78
    Failed(Error),
d9b96952ec60atagen2026-10-25 09:07 UTC79
}
d9b96952ec60atagen2026-10-25 09:07 UTC80
d9b96952ec60atagen2026-10-25 09:07 UTC81
struct Finished {
d9b96952ec60atagen2026-10-25 09:07 UTC82
    stopped: Stopped,
d9b96952ec60atagen2026-10-25 09:07 UTC83
    committed: bool,
d9b96952ec60atagen2026-10-25 09:07 UTC84
    report: Option<HistoryRepairReport>,
80381e06a573atagen2026-10-25 03:02 UTC85
}
80381e06a573atagen2026-10-25 03:02 UTC86
80381e06a573atagen2026-10-25 03:02 UTC87
pub(super) struct RepairController {
80381e06a573atagen2026-10-25 03:02 UTC88
    room: Room,
80381e06a573atagen2026-10-25 03:02 UTC89
    context: SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC90
    running: Option<Running>,
80381e06a573atagen2026-10-25 03:02 UTC91
}
80381e06a573atagen2026-10-25 03:02 UTC92
80381e06a573atagen2026-10-25 03:02 UTC93
impl RepairController {
80381e06a573atagen2026-10-25 03:02 UTC94
    pub(super) fn new(room: Room, context: SessionContext) -> Self {
80381e06a573atagen2026-10-25 03:02 UTC95
        Self {
80381e06a573atagen2026-10-25 03:02 UTC96
            room,
80381e06a573atagen2026-10-25 03:02 UTC97
            context,
80381e06a573atagen2026-10-25 03:02 UTC98
            running: None,
80381e06a573atagen2026-10-25 03:02 UTC99
        }
80381e06a573atagen2026-10-25 03:02 UTC100
    }
80381e06a573atagen2026-10-25 03:02 UTC101
80381e06a573atagen2026-10-25 03:02 UTC102
    pub(super) fn is_running(&self) -> bool {
80381e06a573atagen2026-10-25 03:02 UTC103
        self.running.is_some()
80381e06a573atagen2026-10-25 03:02 UTC104
    }
80381e06a573atagen2026-10-25 03:02 UTC105
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC106
    pub(super) async fn start_recent(&mut self) {
d9b96952ec60atagen2026-10-25 09:07 UTC107
        self.start_recent_with(None).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC108
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC109
80381e06a573atagen2026-10-25 03:02 UTC110
    pub(super) async fn execute(&mut self, command: HistoryRepairCommand, reply: Responder) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC111
        match command {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC112
            HistoryRepairCommand::Cancel { .. } => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC113
                if let Some(running) = self.running.as_mut() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC114
                    running.responders.push(PendingResponder {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC115
                        responder: reply,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC116
                        cancel_ack: true,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC117
                    });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC118
                    running.cancel.send_replace(true);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC119
                } else {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC120
                    reply.done(Ok(()));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC121
                }
80381e06a573atagen2026-10-25 03:02 UTC122
            }
d9b96952ec60atagen2026-10-25 09:07 UTC123
            HistoryRepairCommand::RetryRecent { .. } => self.start_recent_with(Some(reply)).await,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC124
            HistoryRepairCommand::FillGap { gap_id, .. } => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC125
                self.start_gap_fill(gap_id, reply).await;
80381e06a573atagen2026-10-25 03:02 UTC126
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC127
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC128
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC129
d9b96952ec60atagen2026-10-25 09:07 UTC130
    async fn start_recent_with(&mut self, responder: Option<Responder>) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC131
        if let Some(running) = &mut self.running {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC132
            if let Some(responder) = responder {
d9b96952ec60atagen2026-10-25 09:07 UTC133
                if running.gap_id.is_none() {
9eabb6b7587datagen2026-10-25 07:08 UTC134
                    running.responders.push(PendingResponder {
9eabb6b7587datagen2026-10-25 07:08 UTC135
                        responder,
9eabb6b7587datagen2026-10-25 07:08 UTC136
                        cancel_ack: false,
9eabb6b7587datagen2026-10-25 07:08 UTC137
                    });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC138
                } else {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC139
                    responder.done(Err(room_busy()));
80381e06a573atagen2026-10-25 03:02 UTC140
                }
80381e06a573atagen2026-10-25 03:02 UTC141
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC142
            return;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC143
        }
d9b96952ec60atagen2026-10-25 09:07 UTC144
        let event = Event::RecentHistoryChanged {
d9b96952ec60atagen2026-10-25 09:07 UTC145
            room_id: self.room.room_id().to_owned(),
d9b96952ec60atagen2026-10-25 09:07 UTC146
            state: RecentHistoryState::Loading,
d9b96952ec60atagen2026-10-25 09:07 UTC147
        };
d9b96952ec60atagen2026-10-25 09:07 UTC148
        if self.context.events.send(event).await.is_err() {
d9b96952ec60atagen2026-10-25 09:07 UTC149
            if let Some(responder) = responder {
d9b96952ec60atagen2026-10-25 09:07 UTC150
                responder.done(Err(Error::interrupted()));
80381e06a573atagen2026-10-25 03:02 UTC151
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC152
            return;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC153
        }
80381e06a573atagen2026-10-25 03:02 UTC154
        let (cancel, receiver) = watch::channel(false);
80381e06a573atagen2026-10-25 03:02 UTC155
        let room = self.room.clone();
d9b96952ec60atagen2026-10-25 09:07 UTC156
        let task = spawn(async move { load_recent(room, receiver).await });
80381e06a573atagen2026-10-25 03:02 UTC157
        self.running = Some(Running {
80381e06a573atagen2026-10-25 03:02 UTC158
            task,
80381e06a573atagen2026-10-25 03:02 UTC159
            cancel,
d9b96952ec60atagen2026-10-25 09:07 UTC160
            responders: responder
d9b96952ec60atagen2026-10-25 09:07 UTC161
                .map(|responder| PendingResponder {
d9b96952ec60atagen2026-10-25 09:07 UTC162
                    responder,
d9b96952ec60atagen2026-10-25 09:07 UTC163
                    cancel_ack: false,
d9b96952ec60atagen2026-10-25 09:07 UTC164
                })
d9b96952ec60atagen2026-10-25 09:07 UTC165
                .into_iter()
d9b96952ec60atagen2026-10-25 09:07 UTC166
                .collect(),
d9b96952ec60atagen2026-10-25 09:07 UTC167
            gap_id: None,
d9b96952ec60atagen2026-10-25 09:07 UTC168
            closing: false,
80381e06a573atagen2026-10-25 03:02 UTC169
        });
80381e06a573atagen2026-10-25 03:02 UTC170
    }
80381e06a573atagen2026-10-25 03:02 UTC171
d9b96952ec60atagen2026-10-25 09:07 UTC172
    async fn start_gap_fill(&mut self, gap_id: OwnedTransactionId, responder: Responder) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC173
        if self.is_running() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC174
            responder.done(Err(room_busy()));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC175
            return;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC176
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC177
        let room = self.room.clone();
d9b96952ec60atagen2026-10-25 09:07 UTC178
        let progress = report(self.room.room_id().to_owned(), gap_id.clone());
9eabb6b7587datagen2026-10-25 07:08 UTC179
        if self
9eabb6b7587datagen2026-10-25 07:08 UTC180
            .context
9eabb6b7587datagen2026-10-25 07:08 UTC181
            .events
d9b96952ec60atagen2026-10-25 09:07 UTC182
            .send(Event::HistoryRepairChanged(progress.clone()))
9eabb6b7587datagen2026-10-25 07:08 UTC183
            .await
9eabb6b7587datagen2026-10-25 07:08 UTC184
            .is_err()
9eabb6b7587datagen2026-10-25 07:08 UTC185
        {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC186
            responder.done(Err(Error::interrupted()));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC187
            return;
80381e06a573atagen2026-10-25 03:02 UTC188
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC189
        let (cancel, receiver) = watch::channel(false);
d9b96952ec60atagen2026-10-25 09:07 UTC190
        let task = spawn(async move { fill_gap(room, progress, receiver).await });
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC191
        self.running = Some(Running {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC192
            task,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC193
            cancel,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC194
            responders: vec![PendingResponder {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC195
                responder,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC196
                cancel_ack: false,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC197
            }],
d9b96952ec60atagen2026-10-25 09:07 UTC198
            gap_id: Some(gap_id),
d9b96952ec60atagen2026-10-25 09:07 UTC199
            closing: false,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC200
        });
80381e06a573atagen2026-10-25 03:02 UTC201
    }
80381e06a573atagen2026-10-25 03:02 UTC202
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC203
    pub(super) async fn next_completion(&mut self) -> Option<Completion> {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC204
        let running = self.running.as_mut()?;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC205
        let result = (&mut running.task).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC206
        Some(self.finish_task(result))
80381e06a573atagen2026-10-25 03:02 UTC207
    }
80381e06a573atagen2026-10-25 03:02 UTC208
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC209
    pub(super) async fn shutdown(&mut self) -> Option<Completion> {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC210
        let running = self.running.as_mut()?;
d9b96952ec60atagen2026-10-25 09:07 UTC211
        running.closing = true;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC212
        running.cancel.send_replace(true);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC213
        let result = (&mut running.task).await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC214
        Some(self.finish_task(result))
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC215
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC216
d9b96952ec60atagen2026-10-25 09:07 UTC217
    fn finish_task(&mut self, result: Result<Finished, JoinError>) -> Completion {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC218
        let running = self.running.take().expect("repair task exists");
d9b96952ec60atagen2026-10-25 09:07 UTC219
        let finished = result.unwrap_or(Finished {
d9b96952ec60atagen2026-10-25 09:07 UTC220
            stopped: Stopped::Failed(Error::interrupted()),
d9b96952ec60atagen2026-10-25 09:07 UTC221
            committed: false,
d9b96952ec60atagen2026-10-25 09:07 UTC222
            report: None,
d9b96952ec60atagen2026-10-25 09:07 UTC223
        });
d9b96952ec60atagen2026-10-25 09:07 UTC224
        let result = match &finished.stopped {
d9b96952ec60atagen2026-10-25 09:07 UTC225
            Stopped::Done => Ok(CommandOutput::Done),
d9b96952ec60atagen2026-10-25 09:07 UTC226
            Stopped::Cancelled => Err(Error::interrupted()),
d9b96952ec60atagen2026-10-25 09:07 UTC227
            Stopped::Failed(error) => Err(error.clone()),
d9b96952ec60atagen2026-10-25 09:07 UTC228
        };
d9b96952ec60atagen2026-10-25 09:07 UTC229
        let outcome = match running.gap_id {
d9b96952ec60atagen2026-10-25 09:07 UTC230
            None => Outcome::Recent(match finished.stopped {
d9b96952ec60atagen2026-10-25 09:07 UTC231
                _ if running.closing => RecentHistoryState::Idle,
d9b96952ec60atagen2026-10-25 09:07 UTC232
                Stopped::Done => RecentHistoryState::Loaded,
d9b96952ec60atagen2026-10-25 09:07 UTC233
                Stopped::Cancelled => RecentHistoryState::Cancelled,
d9b96952ec60atagen2026-10-25 09:07 UTC234
                Stopped::Failed(error) => RecentHistoryState::Failed(error.message),
d9b96952ec60atagen2026-10-25 09:07 UTC235
            }),
d9b96952ec60atagen2026-10-25 09:07 UTC236
            Some(gap_id) => {
d9b96952ec60atagen2026-10-25 09:07 UTC237
                let mut report = finished
d9b96952ec60atagen2026-10-25 09:07 UTC238
                    .report
d9b96952ec60atagen2026-10-25 09:07 UTC239
                    .unwrap_or_else(|| report(self.room.room_id().to_owned(), gap_id));
d9b96952ec60atagen2026-10-25 09:07 UTC240
                match finished.stopped {
d9b96952ec60atagen2026-10-25 09:07 UTC241
                    Stopped::Done => {}
d9b96952ec60atagen2026-10-25 09:07 UTC242
                    Stopped::Cancelled => report.stop = Some(HistoryRepairStop::Cancelled),
d9b96952ec60atagen2026-10-25 09:07 UTC243
                    Stopped::Failed(error) => report.failure = Some(error),
d9b96952ec60atagen2026-10-25 09:07 UTC244
                }
d9b96952ec60atagen2026-10-25 09:07 UTC245
                report.phase = if report.stop.is_none() && report.failure.is_none() {
d9b96952ec60atagen2026-10-25 09:07 UTC246
                    HistoryRepairPhase::Ready
d9b96952ec60atagen2026-10-25 09:07 UTC247
                } else {
d9b96952ec60atagen2026-10-25 09:07 UTC248
                    HistoryRepairPhase::Paused
d9b96952ec60atagen2026-10-25 09:07 UTC249
                };
d9b96952ec60atagen2026-10-25 09:07 UTC250
                Outcome::Fill(report)
d9b96952ec60atagen2026-10-25 09:07 UTC251
            }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC252
        };
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC253
        Completion {
d9b96952ec60atagen2026-10-25 09:07 UTC254
            outcome,
d9b96952ec60atagen2026-10-25 09:07 UTC255
            committed: finished.committed,
d9b96952ec60atagen2026-10-25 09:07 UTC256
            closing: running.closing,
d9b96952ec60atagen2026-10-25 09:07 UTC257
            result,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC258
            responders: running.responders,
80381e06a573atagen2026-10-25 03:02 UTC259
        }
80381e06a573atagen2026-10-25 03:02 UTC260
    }
80381e06a573atagen2026-10-25 03:02 UTC261
}
80381e06a573atagen2026-10-25 03:02 UTC262
d9b96952ec60atagen2026-10-25 09:07 UTC263
async fn load_recent(room: Room, mut cancel: watch::Receiver<bool>) -> Finished {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC264
    let outcome = tokio::select! {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC265
        biased;
d9b96952ec60atagen2026-10-25 09:07 UTC266
        _ = cancel.changed() => None,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC267
        result = async {
d9b96952ec60atagen2026-10-25 09:07 UTC268
            let (cache, _handles) = room.event_cache().await?;
d9b96952ec60atagen2026-10-25 09:07 UTC269
            cache.load_recent_history().await
d9b96952ec60atagen2026-10-25 09:07 UTC270
        } => Some(result),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC271
    };
d9b96952ec60atagen2026-10-25 09:07 UTC272
    let stopped = match outcome {
d9b96952ec60atagen2026-10-25 09:07 UTC273
        None => Stopped::Cancelled,
d9b96952ec60atagen2026-10-25 09:07 UTC274
        Some(Ok(_)) => Stopped::Done,
d9b96952ec60atagen2026-10-25 09:07 UTC275
        Some(Err(error)) => Stopped::Failed(cache_error(error)),
d9b96952ec60atagen2026-10-25 09:07 UTC276
    };
d9b96952ec60atagen2026-10-25 09:07 UTC277
    Finished {
d9b96952ec60atagen2026-10-25 09:07 UTC278
        committed: matches!(stopped, Stopped::Done),
d9b96952ec60atagen2026-10-25 09:07 UTC279
        stopped,
d9b96952ec60atagen2026-10-25 09:07 UTC280
        report: None,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC281
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC282
}
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC283
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC284
async fn fill_gap(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC285
    room: Room,
d9b96952ec60atagen2026-10-25 09:07 UTC286
    mut report: HistoryRepairReport,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC287
    mut cancel: watch::Receiver<bool>,
d9b96952ec60atagen2026-10-25 09:07 UTC288
) -> Finished {
d9b96952ec60atagen2026-10-25 09:07 UTC289
    let mut committed = false;
d9b96952ec60atagen2026-10-25 09:07 UTC290
    let stopped = async {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC291
        let (cache, _handles) = room.event_cache().await.map_err(cache_error)?;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC292
        for _ in 0..MAX_GAP_PAGES {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC293
            let next = tokio::select! {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC294
                biased;
d9b96952ec60atagen2026-10-25 09:07 UTC295
                _ = cancel.changed() => return Ok(Stopped::Cancelled),
d9b96952ec60atagen2026-10-25 09:07 UTC296
                result = cache.fill_history_gap_once(report.gap_id.clone()) => {
d9b96952ec60atagen2026-10-25 09:07 UTC297
                    result.map_err(cache_error)?
d9b96952ec60atagen2026-10-25 09:07 UTC298
                }
80381e06a573atagen2026-10-25 03:02 UTC299
            };
d9b96952ec60atagen2026-10-25 09:07 UTC300
            let (gap, events) = match next {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC301
                HistoryGapFillOutcome::NotFound => {
d9b96952ec60atagen2026-10-25 09:07 UTC302
                    return Err(Error::new(
d9b96952ec60atagen2026-10-25 09:07 UTC303
                        ErrorKind::EventNotFound,
d9b96952ec60atagen2026-10-25 09:07 UTC304
                        "history gap was not found",
d9b96952ec60atagen2026-10-25 09:07 UTC305
                    ));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC306
                }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC307
                HistoryGapFillOutcome::Busy => return Err(room_busy()),
d9b96952ec60atagen2026-10-25 09:07 UTC308
                HistoryGapFillOutcome::Updated { gap, events } => (gap, events),
d9b96952ec60atagen2026-10-25 09:07 UTC309
            };
06c73391cbaeatagen2026-10-25 10:26 UTC310
            // the sdk answers an already unavailable gap without asking the server, and a joined
06c73391cbaeatagen2026-10-25 10:26 UTC311
            // one with NotFound. an empty first page that hits the end looks the same, uncounted
924b7c011591atagen2026-10-25 10:28 UTC312
            let settled_before =
924b7c011591atagen2026-10-25 10:28 UTC313
                report.pages == 0 && events == 0 && gap.status == HistoryGapStatus::Unavailable;
d9b96952ec60atagen2026-10-25 09:07 UTC314
            if !settled_before {
d9b96952ec60atagen2026-10-25 09:07 UTC315
                report.pages += 1;
d9b96952ec60atagen2026-10-25 09:07 UTC316
                committed = true;
d9b96952ec60atagen2026-10-25 09:07 UTC317
            }
d9b96952ec60atagen2026-10-25 09:07 UTC318
            report.events = report
d9b96952ec60atagen2026-10-25 09:07 UTC319
                .events
d9b96952ec60atagen2026-10-25 09:07 UTC320
                .saturating_add(u32::try_from(events).unwrap_or(u32::MAX));
d9b96952ec60atagen2026-10-25 09:07 UTC321
            match gap.status {
d9b96952ec60atagen2026-10-25 09:07 UTC322
                HistoryGapStatus::Joined => {
d9b96952ec60atagen2026-10-25 09:07 UTC323
                    report.continuity = HistoryContinuity::Verified;
d9b96952ec60atagen2026-10-25 09:07 UTC324
                    return Ok(Stopped::Done);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC325
                }
d9b96952ec60atagen2026-10-25 09:07 UTC326
                HistoryGapStatus::Unavailable => {
d9b96952ec60atagen2026-10-25 09:07 UTC327
                    report.continuity = HistoryContinuity::AccessibleStart;
d9b96952ec60atagen2026-10-25 09:07 UTC328
                    report.stop = Some(HistoryRepairStop::HistoryUnavailable);
d9b96952ec60atagen2026-10-25 09:07 UTC329
                    return Ok(Stopped::Done);
d9b96952ec60atagen2026-10-25 09:07 UTC330
                }
d9b96952ec60atagen2026-10-25 09:07 UTC331
                HistoryGapStatus::Paused => {
d9b96952ec60atagen2026-10-25 09:07 UTC332
                    report.stop = Some(HistoryRepairStop::CursorStalled);
d9b96952ec60atagen2026-10-25 09:07 UTC333
                    return Ok(Stopped::Done);
d9b96952ec60atagen2026-10-25 09:07 UTC334
                }
d9b96952ec60atagen2026-10-25 09:07 UTC335
                HistoryGapStatus::Failed { message } => {
d9b96952ec60atagen2026-10-25 09:07 UTC336
                    return Err(Error::new(ErrorKind::Network, message));
d9b96952ec60atagen2026-10-25 09:07 UTC337
                }
d9b96952ec60atagen2026-10-25 09:07 UTC338
                HistoryGapStatus::Idle => {}
80381e06a573atagen2026-10-25 03:02 UTC339
            }
80381e06a573atagen2026-10-25 03:02 UTC340
        }
d9b96952ec60atagen2026-10-25 09:07 UTC341
        report.stop = Some(HistoryRepairStop::Budget);
d9b96952ec60atagen2026-10-25 09:07 UTC342
        Ok(Stopped::Done)
80381e06a573atagen2026-10-25 03:02 UTC343
    }
d9b96952ec60atagen2026-10-25 09:07 UTC344
    .await
d9b96952ec60atagen2026-10-25 09:07 UTC345
    .unwrap_or_else(Stopped::Failed);
d9b96952ec60atagen2026-10-25 09:07 UTC346
    Finished {
d9b96952ec60atagen2026-10-25 09:07 UTC347
        stopped,
d9b96952ec60atagen2026-10-25 09:07 UTC348
        committed,
d9b96952ec60atagen2026-10-25 09:07 UTC349
        report: Some(report),
80381e06a573atagen2026-10-25 03:02 UTC350
    }
80381e06a573atagen2026-10-25 03:02 UTC351
}
80381e06a573atagen2026-10-25 03:02 UTC352
d9b96952ec60atagen2026-10-25 09:07 UTC353
fn report(room_id: OwnedRoomId, gap_id: OwnedTransactionId) -> HistoryRepairReport {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC354
    HistoryRepairReport {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC355
        room_id,
d9b96952ec60atagen2026-10-25 09:07 UTC356
        gap_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC357
        pages: 0,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC358
        events: 0,
d9b96952ec60atagen2026-10-25 09:07 UTC359
        phase: HistoryRepairPhase::Bridging,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC360
        continuity: HistoryContinuity::Unresolved,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC361
        stop: None,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC362
        failure: None,
80381e06a573atagen2026-10-25 03:02 UTC363
    }
80381e06a573atagen2026-10-25 03:02 UTC364
}
80381e06a573atagen2026-10-25 03:02 UTC365
d9b96952ec60atagen2026-10-25 09:07 UTC366
fn cache_error(source: EventCacheError) -> Error {
d9b96952ec60atagen2026-10-25 09:07 UTC367
    let kind = match &source {
d9b96952ec60atagen2026-10-25 09:07 UTC368
        EventCacheError::PaginationError(error) => crate::error::classify_sdk_error(error),
d9b96952ec60atagen2026-10-25 09:07 UTC369
        EventCacheError::InitialPaginationError(_) => ErrorKind::Network,
d9b96952ec60atagen2026-10-25 09:07 UTC370
        EventCacheError::RoomNotFound { .. } => ErrorKind::RoomUnknown,
d9b96952ec60atagen2026-10-25 09:07 UTC371
        EventCacheError::ClientDropped => ErrorKind::Interrupted,
d9b96952ec60atagen2026-10-25 09:07 UTC372
        _ => ErrorKind::Store,
d9b96952ec60atagen2026-10-25 09:07 UTC373
    };
d9b96952ec60atagen2026-10-25 09:07 UTC374
    Error::with_source(kind, "room history operation failed", source)
80381e06a573atagen2026-10-25 03:02 UTC375
}
80381e06a573atagen2026-10-25 03:02 UTC376
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC377
fn room_busy() -> Error {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC378
    Error::new(
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC379
        ErrorKind::RoomBusy,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC380
        "room history operation is already running",
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC381
    )
80381e06a573atagen2026-10-25 03:02 UTC382
}