atagen

macltok

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

Blame: src/engine/room.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
use std::collections::{HashMap, VecDeque};
31ce2453c82fatagen2026-09-24 13:07 UTC2
31ce2453c82fatagen2026-09-24 13:07 UTC3
use matrix_sdk::{Room, RoomState};
31ce2453c82fatagen2026-09-24 13:07 UTC4
use matrix_sdk_ui::timeline::{RoomExt, Timeline, TimelineFocus, TimelineReadReceiptTracking};
fa5b4e09943aatagen2026-10-24 09:33 UTC5
use ruma::OwnedRoomId;
1fe480c16a3catagen2026-09-26 08:44 UTC6
use tokio::sync::mpsc;
31ce2453c82fatagen2026-09-24 13:07 UTC7
2819830aef32atagen2026-10-26 08:42 UTC8
use super::executor::{AbortHandle, JoinError, TaskSet, is_panic, label, resume_unwind};
d9b96952ec60atagen2026-10-25 09:07 UTC9
use super::repair::{Completion, Outcome, RepairController};
5a52012c9b1datagen2026-09-24 23:45 UTC10
use super::timeline::{SessionContext, TaskGuard, TimelineSession};
31ce2453c82fatagen2026-09-24 13:07 UTC11
use crate::{
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC12
    Error, ErrorKind, Event, HistoryRepairCommand, RecentHistoryState, TimelineCommand, TimelineId,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC13
    TypingState, transport::Responder,
31ce2453c82fatagen2026-09-24 13:07 UTC14
};
31ce2453c82fatagen2026-09-24 13:07 UTC15
123824465d8batagen2026-10-26 09:18 UTC16
/// a build stuck on the event cache would otherwise hold the room's open forever. a big room on
123824465d8batagen2026-10-26 09:18 UTC17
/// a slow device can take tens of seconds to load from IndexedDB, so this is generous
123824465d8batagen2026-10-26 09:18 UTC18
const BUILD_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60);
123824465d8batagen2026-10-26 09:18 UTC19
31ce2453c82fatagen2026-09-24 13:07 UTC20
pub(super) enum ActorRequest {
31ce2453c82fatagen2026-09-24 13:07 UTC21
    /// opening an open room answers with a fresh reset
31ce2453c82fatagen2026-09-24 13:07 UTC22
    Open(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC23
    Execute(TimelineCommand, Responder),
80381e06a573atagen2026-10-25 03:02 UTC24
    Repair(HistoryRepairCommand, Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC25
    Close(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC26
}
31ce2453c82fatagen2026-09-24 13:07 UTC27
31ce2453c82fatagen2026-09-24 13:07 UTC28
impl ActorRequest {
31ce2453c82fatagen2026-09-24 13:07 UTC29
    pub(super) fn into_reply(self) -> Responder {
31ce2453c82fatagen2026-09-24 13:07 UTC30
        match self {
80381e06a573atagen2026-10-25 03:02 UTC31
            Self::Open(reply)
80381e06a573atagen2026-10-25 03:02 UTC32
            | Self::Execute(_, reply)
80381e06a573atagen2026-10-25 03:02 UTC33
            | Self::Repair(_, reply)
80381e06a573atagen2026-10-25 03:02 UTC34
            | Self::Close(reply) => reply,
31ce2453c82fatagen2026-09-24 13:07 UTC35
        }
31ce2453c82fatagen2026-09-24 13:07 UTC36
    }
31ce2453c82fatagen2026-09-24 13:07 UTC37
}
31ce2453c82fatagen2026-09-24 13:07 UTC38
5a52012c9b1datagen2026-09-24 23:45 UTC39
pub(super) enum ActorNote {
5a52012c9b1datagen2026-09-24 23:45 UTC40
    /// stopping on its own, after a leave or a kick. anything sent its way now comes back in `Exited`
5a52012c9b1datagen2026-09-24 23:45 UTC41
    Leaving {
5a52012c9b1datagen2026-09-24 23:45 UTC42
        room_id: OwnedRoomId,
5a52012c9b1datagen2026-09-24 23:45 UTC43
        generation: u64,
5a52012c9b1datagen2026-09-24 23:45 UTC44
    },
5a52012c9b1datagen2026-09-24 23:45 UTC45
    Exited {
5a52012c9b1datagen2026-09-24 23:45 UTC46
        room_id: OwnedRoomId,
5a52012c9b1datagen2026-09-24 23:45 UTC47
        generation: u64,
5a52012c9b1datagen2026-09-24 23:45 UTC48
        /// requests that arrived too late to be served, for the engine to answer or replay
5a52012c9b1datagen2026-09-24 23:45 UTC49
        leftovers: Vec<ActorRequest>,
5a52012c9b1datagen2026-09-24 23:45 UTC50
    },
31ce2453c82fatagen2026-09-24 13:07 UTC51
}
31ce2453c82fatagen2026-09-24 13:07 UTC52
31ce2453c82fatagen2026-09-24 13:07 UTC53
pub(super) struct RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC54
    pub(super) room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC55
    pub(super) generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC56
    pub(super) room: Room,
31ce2453c82fatagen2026-09-24 13:07 UTC57
    pub(super) requests: mpsc::Receiver<ActorRequest>,
5a52012c9b1datagen2026-09-24 23:45 UTC58
    pub(super) notes: mpsc::UnboundedSender<ActorNote>,
31ce2453c82fatagen2026-09-24 13:07 UTC59
    pub(super) context: SessionContext,
31ce2453c82fatagen2026-09-24 13:07 UTC60
    pub(super) panics: mpsc::UnboundedReceiver<&'static str>,
31ce2453c82fatagen2026-09-24 13:07 UTC61
    pub(super) opening: Vec<Responder>,
31ce2453c82fatagen2026-09-24 13:07 UTC62
}
31ce2453c82fatagen2026-09-24 13:07 UTC63
31ce2453c82fatagen2026-09-24 13:07 UTC64
impl RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC65
    pub(super) async fn run(mut self) {
5a52012c9b1datagen2026-09-24 23:45 UTC66
        self.serve().await;
5a52012c9b1datagen2026-09-24 23:45 UTC67
        self.requests.close();
5a52012c9b1datagen2026-09-24 23:45 UTC68
        let mut leftovers = Vec::new();
5a52012c9b1datagen2026-09-24 23:45 UTC69
        while let Ok(request) = self.requests.try_recv() {
5a52012c9b1datagen2026-09-24 23:45 UTC70
            leftovers.push(request);
5a52012c9b1datagen2026-09-24 23:45 UTC71
        }
5a52012c9b1datagen2026-09-24 23:45 UTC72
        let _ = self.notes.send(ActorNote::Exited {
31ce2453c82fatagen2026-09-24 13:07 UTC73
            room_id: self.room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC74
            generation: self.generation,
5a52012c9b1datagen2026-09-24 23:45 UTC75
            leftovers,
5a52012c9b1datagen2026-09-24 23:45 UTC76
        });
31ce2453c82fatagen2026-09-24 13:07 UTC77
    }
31ce2453c82fatagen2026-09-24 13:07 UTC78
31ce2453c82fatagen2026-09-24 13:07 UTC79
    async fn serve(&mut self) {
2819830aef32atagen2026-10-26 08:42 UTC80
        tracing::debug!(room_id = %self.room_id, "building the room timeline");
123824465d8batagen2026-10-26 09:18 UTC81
        let build = build_timeline(
31ce2453c82fatagen2026-09-24 13:07 UTC82
            &self.room,
31ce2453c82fatagen2026-09-24 13:07 UTC83
            TimelineFocus::Live {
31ce2453c82fatagen2026-09-24 13:07 UTC84
                hide_threaded_events: true,
31ce2453c82fatagen2026-09-24 13:07 UTC85
            },
123824465d8batagen2026-10-26 09:18 UTC86
        );
123824465d8batagen2026-10-26 09:18 UTC87
        let timeout = matrix_sdk::sleep::sleep(BUILD_TIMEOUT);
123824465d8batagen2026-10-26 09:18 UTC88
        let timeline = tokio::select! {
123824465d8batagen2026-10-26 09:18 UTC89
            built = build => match built {
123824465d8batagen2026-10-26 09:18 UTC90
                Ok(timeline) => timeline,
123824465d8batagen2026-10-26 09:18 UTC91
                Err(error) => {
123824465d8batagen2026-10-26 09:18 UTC92
                    tracing::warn!(%error, room_id = %self.room_id, "room actor could not build its timeline");
123824465d8batagen2026-10-26 09:18 UTC93
                    let error = Error::with_source(ErrorKind::Timeline, "room timeline build failed", error);
123824465d8batagen2026-10-26 09:18 UTC94
                    return self.refuse_everything(error).await;
123824465d8batagen2026-10-26 09:18 UTC95
                }
123824465d8batagen2026-10-26 09:18 UTC96
            },
123824465d8batagen2026-10-26 09:18 UTC97
            () = timeout => {
123824465d8batagen2026-10-26 09:18 UTC98
                tracing::warn!(room_id = %self.room_id, "room timeline build timed out");
123824465d8batagen2026-10-26 09:18 UTC99
                let error = Error::new(ErrorKind::Timeline, "room timeline build timed out");
123824465d8batagen2026-10-26 09:18 UTC100
                return self.refuse_everything(error).await;
123824465d8batagen2026-10-26 09:18 UTC101
            }
31ce2453c82fatagen2026-09-24 13:07 UTC102
        };
2819830aef32atagen2026-10-26 08:42 UTC103
        tracing::debug!(room_id = %self.room_id, "starting the room timeline session");
31ce2453c82fatagen2026-09-24 13:07 UTC104
        let Ok(room_timeline) = TimelineSession::start(
31ce2453c82fatagen2026-09-24 13:07 UTC105
            TimelineId::Room(self.room_id.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC106
            timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC107
            self.context.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC108
        )
31ce2453c82fatagen2026-09-24 13:07 UTC109
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC110
        else {
31ce2453c82fatagen2026-09-24 13:07 UTC111
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC112
        };
31ce2453c82fatagen2026-09-24 13:07 UTC113
        let (typing_guard, typing) = self.room.subscribe_to_typing_notifications();
0bfa3f46f0beatagen2026-09-26 09:12 UTC114
        let mut typing_watcher = TaskGuard(self.context.supervisor.spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC115
            "typing watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC116
            watch_typing(self.room_id.clone(), typing, self.context.events.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC117
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC118
        let mut timelines = Timelines {
31ce2453c82fatagen2026-09-24 13:07 UTC119
            room: room_timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC120
            threads: Threads::new(self.requests.max_capacity()),
1fe480c16a3catagen2026-09-26 08:44 UTC121
            work: TaskSet::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC122
        };
80381e06a573atagen2026-10-25 03:02 UTC123
        let mut repair = RepairController::new(self.room.clone(), self.context.clone());
2819830aef32atagen2026-10-26 08:42 UTC124
        tracing::debug!(room_id = %self.room_id, "starting recent history");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC125
        repair.start_recent().await;
2819830aef32atagen2026-10-26 08:42 UTC126
        tracing::debug!(room_id = %self.room_id, "room opened");
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC127
        for reply in self.opening.drain(..) {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC128
            reply.done(Ok(()));
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC129
        }
80381e06a573atagen2026-10-25 03:02 UTC130
        let close = self.serve_open(&mut timelines, &mut repair).await;
d9b96952ec60atagen2026-10-25 09:07 UTC131
        let recent_settled = match repair.shutdown().await {
d9b96952ec60atagen2026-10-25 09:07 UTC132
            Some(completion) => {
d9b96952ec60atagen2026-10-25 09:07 UTC133
                let recent = matches!(completion.outcome, Outcome::Recent(_));
d9b96952ec60atagen2026-10-25 09:07 UTC134
                self.finish_repair(completion, &mut timelines).await;
d9b96952ec60atagen2026-10-25 09:07 UTC135
                recent
d9b96952ec60atagen2026-10-25 09:07 UTC136
            }
d9b96952ec60atagen2026-10-25 09:07 UTC137
            None => false,
d9b96952ec60atagen2026-10-25 09:07 UTC138
        };
d9b96952ec60atagen2026-10-25 09:07 UTC139
        if !recent_settled {
d9b96952ec60atagen2026-10-25 09:07 UTC140
            let _ = self
d9b96952ec60atagen2026-10-25 09:07 UTC141
                .context
d9b96952ec60atagen2026-10-25 09:07 UTC142
                .events
d9b96952ec60atagen2026-10-25 09:07 UTC143
                .send(Event::RecentHistoryChanged {
d9b96952ec60atagen2026-10-25 09:07 UTC144
                    room_id: self.room_id.clone(),
d9b96952ec60atagen2026-10-25 09:07 UTC145
                    state: RecentHistoryState::Idle,
d9b96952ec60atagen2026-10-25 09:07 UTC146
                })
d9b96952ec60atagen2026-10-25 09:07 UTC147
                .await;
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC148
        }
5a52012c9b1datagen2026-09-24 23:45 UTC149
        if close.is_none() {
5a52012c9b1datagen2026-09-24 23:45 UTC150
            self.requests.close();
5a52012c9b1datagen2026-09-24 23:45 UTC151
            let _ = self.notes.send(ActorNote::Leaving {
5a52012c9b1datagen2026-09-24 23:45 UTC152
                room_id: self.room_id.clone(),
5a52012c9b1datagen2026-09-24 23:45 UTC153
                generation: self.generation,
5a52012c9b1datagen2026-09-24 23:45 UTC154
            });
5a52012c9b1datagen2026-09-24 23:45 UTC155
        }
31ce2453c82fatagen2026-09-24 13:07 UTC156
31ce2453c82fatagen2026-09-24 13:07 UTC157
        // let redactions and receipts that already left finish rather than lie about them
31ce2453c82fatagen2026-09-24 13:07 UTC158
        while timelines.work.join_next().await.is_some() {}
31ce2453c82fatagen2026-09-24 13:07 UTC159
        drop(typing_guard);
0bfa3f46f0beatagen2026-09-26 09:12 UTC160
        typing_watcher.0.abort();
0bfa3f46f0beatagen2026-09-26 09:12 UTC161
        let _ = (&mut typing_watcher.0).await;
0bfa3f46f0beatagen2026-09-26 09:12 UTC162
        let _ = self
0bfa3f46f0beatagen2026-09-26 09:12 UTC163
            .context
0bfa3f46f0beatagen2026-09-26 09:12 UTC164
            .events
0bfa3f46f0beatagen2026-09-26 09:12 UTC165
            .send(Event::TypingChanged(TypingState {
0bfa3f46f0beatagen2026-09-26 09:12 UTC166
                room_id: self.room_id.clone(),
0bfa3f46f0beatagen2026-09-26 09:12 UTC167
                users: Vec::new(),
0bfa3f46f0beatagen2026-09-26 09:12 UTC168
            }))
0bfa3f46f0beatagen2026-09-26 09:12 UTC169
            .await;
31ce2453c82fatagen2026-09-24 13:07 UTC170
        timelines.threads.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC171
        timelines.room.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC172
        if let Some(reply) = close {
31ce2453c82fatagen2026-09-24 13:07 UTC173
            reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC174
        }
31ce2453c82fatagen2026-09-24 13:07 UTC175
    }
31ce2453c82fatagen2026-09-24 13:07 UTC176
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC177
    async fn finish_repair(&self, mut completion: Completion, timelines: &mut Timelines) {
d9b96952ec60atagen2026-10-25 09:07 UTC178
        let mut cancellation = Ok(());
002c79e4c6ecatagen2026-10-25 10:42 UTC179
        // pages a failed or cancelled operation already committed still have to be on
002c79e4c6ecatagen2026-10-25 10:42 UTC180
        // screen before anyone hears it stopped
d9b96952ec60atagen2026-10-25 09:07 UTC181
        if completion.committed
d9b96952ec60atagen2026-10-25 09:07 UTC182
            && !completion.closing
d9b96952ec60atagen2026-10-25 09:07 UTC183
            && let Err(error) = timelines.room.publish_history().await
d9b96952ec60atagen2026-10-25 09:07 UTC184
        {
002c79e4c6ecatagen2026-10-25 10:42 UTC185
            if completion.result.is_ok() {
002c79e4c6ecatagen2026-10-25 10:42 UTC186
                completion.unpublished(error.clone());
002c79e4c6ecatagen2026-10-25 10:42 UTC187
            }
d9b96952ec60atagen2026-10-25 09:07 UTC188
            cancellation = Err(error);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC189
        }
d9b96952ec60atagen2026-10-25 09:07 UTC190
        let event = match &completion.outcome {
d9b96952ec60atagen2026-10-25 09:07 UTC191
            Outcome::Recent(state) => Event::RecentHistoryChanged {
d9b96952ec60atagen2026-10-25 09:07 UTC192
                room_id: self.room_id.clone(),
d9b96952ec60atagen2026-10-25 09:07 UTC193
                state: state.clone(),
d9b96952ec60atagen2026-10-25 09:07 UTC194
            },
d9b96952ec60atagen2026-10-25 09:07 UTC195
            Outcome::Fill(report) => Event::HistoryRepairChanged(report.clone()),
d9b96952ec60atagen2026-10-25 09:07 UTC196
        };
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC197
        let mut resolved = completion.result.clone();
d9b96952ec60atagen2026-10-25 09:07 UTC198
        let events = &self.context.events;
d9b96952ec60atagen2026-10-25 09:07 UTC199
        if events.send(event).await.is_err() || events.publish_barrier().await.is_err() {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC200
            resolved = Err(Error::interrupted());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC201
            cancellation = Err(Error::interrupted());
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC202
        }
d9b96952ec60atagen2026-10-25 09:07 UTC203
        let limit = self.context.initial_history_limit;
d9b96952ec60atagen2026-10-25 09:07 UTC204
        if matches!(
d9b96952ec60atagen2026-10-25 09:07 UTC205
            completion.outcome,
d9b96952ec60atagen2026-10-25 09:07 UTC206
            Outcome::Recent(RecentHistoryState::Failed(_))
d9b96952ec60atagen2026-10-25 09:07 UTC207
        ) && limit > 0
002c79e4c6ecatagen2026-10-25 10:42 UTC208
            && !timelines.room.has_events().await
d9b96952ec60atagen2026-10-25 09:07 UTC209
        {
d9b96952ec60atagen2026-10-25 09:07 UTC210
            timelines.room.paginate(limit, None);
d9b96952ec60atagen2026-10-25 09:07 UTC211
        }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC212
        completion.resolve(resolved, cancellation);
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC213
    }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC214
31ce2453c82fatagen2026-09-24 13:07 UTC215
    /// returns the close request's responder, if a close is why we stopped
80381e06a573atagen2026-10-25 03:02 UTC216
    async fn serve_open(
80381e06a573atagen2026-10-25 03:02 UTC217
        &mut self,
80381e06a573atagen2026-10-25 03:02 UTC218
        timelines: &mut Timelines,
80381e06a573atagen2026-10-25 03:02 UTC219
        repair: &mut RepairController,
80381e06a573atagen2026-10-25 03:02 UTC220
    ) -> Option<Responder> {
31ce2453c82fatagen2026-09-24 13:07 UTC221
        let mut room_info = self.room.subscribe_info();
5a52012c9b1datagen2026-09-24 23:45 UTC222
        // a leave that landed while the timeline was building won't come round again
5a52012c9b1datagen2026-09-24 23:45 UTC223
        if matches!(self.room.state(), RoomState::Left | RoomState::Banned) {
5a52012c9b1datagen2026-09-24 23:45 UTC224
            return None;
5a52012c9b1datagen2026-09-24 23:45 UTC225
        }
aced11e80b47atagen2026-10-27 00:35 UTC226
        timelines.work.spawn(probe_sync_hole(self.room.clone()));
aced11e80b47atagen2026-10-27 00:35 UTC227
        let mut probe = Box::pin(matrix_sdk::sleep::sleep(PROBE_INTERVAL));
31ce2453c82fatagen2026-09-24 13:07 UTC228
        loop {
31ce2453c82fatagen2026-09-24 13:07 UTC229
            tokio::select! {
aced11e80b47atagen2026-10-27 00:35 UTC230
                () = &mut probe => {
aced11e80b47atagen2026-10-27 00:35 UTC231
                    timelines.work.spawn(probe_sync_hole(self.room.clone()));
aced11e80b47atagen2026-10-27 00:35 UTC232
                    probe = Box::pin(matrix_sdk::sleep::sleep(PROBE_INTERVAL));
aced11e80b47atagen2026-10-27 00:35 UTC233
                }
31ce2453c82fatagen2026-09-24 13:07 UTC234
                Some(info) = room_info.next() => {
31ce2453c82fatagen2026-09-24 13:07 UTC235
                    if matches!(info.state(), RoomState::Left | RoomState::Banned) {
31ce2453c82fatagen2026-09-24 13:07 UTC236
                        tracing::debug!(room_id = %self.room_id, "room actor stopped because we left the room");
31ce2453c82fatagen2026-09-24 13:07 UTC237
                        return None;
31ce2453c82fatagen2026-09-24 13:07 UTC238
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC239
                }
31ce2453c82fatagen2026-09-24 13:07 UTC240
                request = self.requests.recv() => match request {
31ce2453c82fatagen2026-09-24 13:07 UTC241
                    None => return None,
31ce2453c82fatagen2026-09-24 13:07 UTC242
                    Some(ActorRequest::Close(reply)) => return Some(reply),
a951cb7cb7caatagen2026-10-24 23:17 UTC243
                    Some(ActorRequest::Open(reply)) => reply.done(timelines.room.resync().await),
31ce2453c82fatagen2026-09-24 13:07 UTC244
                    Some(ActorRequest::Execute(command, reply)) => {
2819830aef32atagen2026-10-26 08:42 UTC245
                        let label = label(&command);
2819830aef32atagen2026-10-26 08:42 UTC246
                        tracing::debug!(room_id = %self.room_id, command = %label, "room command");
31ce2453c82fatagen2026-09-24 13:07 UTC247
                        timelines.execute(command, reply, &self.room).await;
2819830aef32atagen2026-10-26 08:42 UTC248
                        tracing::debug!(room_id = %self.room_id, command = %label, "room command handed off");
31ce2453c82fatagen2026-09-24 13:07 UTC249
                    }
80381e06a573atagen2026-10-25 03:02 UTC250
                    Some(ActorRequest::Repair(command, reply)) => {
2819830aef32atagen2026-10-26 08:42 UTC251
                        let label = label(&command);
2819830aef32atagen2026-10-26 08:42 UTC252
                        tracing::debug!(room_id = %self.room_id, command = %label, "room repair command");
80381e06a573atagen2026-10-25 03:02 UTC253
                        repair.execute(command, reply).await;
2819830aef32atagen2026-10-26 08:42 UTC254
                        tracing::debug!(room_id = %self.room_id, command = %label, "room repair command handed off");
80381e06a573atagen2026-10-25 03:02 UTC255
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC256
                },
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC257
                completion = repair.next_completion(), if repair.is_running() => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC258
                    if let Some(completion) = completion {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC259
                        self.finish_repair(completion, timelines).await;
80381e06a573atagen2026-10-25 03:02 UTC260
                    }
80381e06a573atagen2026-10-25 03:02 UTC261
                }
31ce2453c82fatagen2026-09-24 13:07 UTC262
                Some(built) = timelines.threads.builds.join_next(), if !timelines.threads.builds.is_empty() => {
31ce2453c82fatagen2026-09-24 13:07 UTC263
                    timelines.thread_built(built, &self.room_id, &self.context).await;
31ce2453c82fatagen2026-09-24 13:07 UTC264
                }
31ce2453c82fatagen2026-09-24 13:07 UTC265
                Some(label) = self.panics.recv() => panic!("{label} panicked in room {}", self.room_id),
31ce2453c82fatagen2026-09-24 13:07 UTC266
                Some(done) = timelines.work.join_next(), if !timelines.work.is_empty() => {
1fe480c16a3catagen2026-09-26 08:44 UTC267
                    if let Err(error) = done && is_panic(&error) {
31ce2453c82fatagen2026-09-24 13:07 UTC268
                        tracing::warn!(room_id = %self.room_id, %error, "timeline operation panicked");
31ce2453c82fatagen2026-09-24 13:07 UTC269
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC270
                }
31ce2453c82fatagen2026-09-24 13:07 UTC271
            }
31ce2453c82fatagen2026-09-24 13:07 UTC272
        }
31ce2453c82fatagen2026-09-24 13:07 UTC273
    }
31ce2453c82fatagen2026-09-24 13:07 UTC274
123824465d8batagen2026-10-26 09:18 UTC275
    async fn refuse_everything(&mut self, error: Error) {
31ce2453c82fatagen2026-09-24 13:07 UTC276
        for reply in self.opening.drain(..) {
31ce2453c82fatagen2026-09-24 13:07 UTC277
            reply.done(Err(error.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC278
        }
31ce2453c82fatagen2026-09-24 13:07 UTC279
        self.requests.close();
31ce2453c82fatagen2026-09-24 13:07 UTC280
        while let Some(request) = self.requests.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC281
            request.into_reply().done(Err(error.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC282
        }
31ce2453c82fatagen2026-09-24 13:07 UTC283
    }
31ce2453c82fatagen2026-09-24 13:07 UTC284
}
31ce2453c82fatagen2026-09-24 13:07 UTC285
aced11e80b47atagen2026-10-27 00:35 UTC286
/// a server that under-reports `limited` hides skipped events behind a sync token the sdk drops,
aced11e80b47atagen2026-10-27 00:35 UTC287
/// so an open room looks behind the newest one now and then. without one recorded it costs nothing
aced11e80b47atagen2026-10-27 00:35 UTC288
const PROBE_INTERVAL: std::time::Duration = std::time::Duration::from_secs(30);
aced11e80b47atagen2026-10-27 00:35 UTC289
aced11e80b47atagen2026-10-27 00:35 UTC290
async fn probe_sync_hole(room: Room) {
aced11e80b47atagen2026-10-27 00:35 UTC291
    let result = async {
aced11e80b47atagen2026-10-27 00:35 UTC292
        let (cache, _handles) = room.event_cache().await?;
aced11e80b47atagen2026-10-27 00:35 UTC293
        cache.probe_sync_hole().await
aced11e80b47atagen2026-10-27 00:35 UTC294
    }
aced11e80b47atagen2026-10-27 00:35 UTC295
    .await;
aced11e80b47atagen2026-10-27 00:35 UTC296
    match result {
aced11e80b47atagen2026-10-27 00:35 UTC297
        Ok(Some(found)) if found > 0 => {
aced11e80b47atagen2026-10-27 00:35 UTC298
            tracing::info!(room_id = %room.room_id(), found, "history gap probe: new events");
aced11e80b47atagen2026-10-27 00:35 UTC299
        }
aced11e80b47atagen2026-10-27 00:35 UTC300
        Ok(_) => {}
aced11e80b47atagen2026-10-27 00:35 UTC301
        Err(error) => {
aced11e80b47atagen2026-10-27 00:35 UTC302
            tracing::debug!(room_id = %room.room_id(), %error, "history gap probe failed")
aced11e80b47atagen2026-10-27 00:35 UTC303
        }
aced11e80b47atagen2026-10-27 00:35 UTC304
    }
aced11e80b47atagen2026-10-27 00:35 UTC305
}
aced11e80b47atagen2026-10-27 00:35 UTC306
31ce2453c82fatagen2026-09-24 13:07 UTC307
async fn build_timeline(
31ce2453c82fatagen2026-09-24 13:07 UTC308
    room: &Room,
31ce2453c82fatagen2026-09-24 13:07 UTC309
    focus: TimelineFocus,
31ce2453c82fatagen2026-09-24 13:07 UTC310
) -> Result<Timeline, matrix_sdk_ui::timeline::Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC311
    room.timeline_builder()
31ce2453c82fatagen2026-09-24 13:07 UTC312
        .with_focus(focus)
31ce2453c82fatagen2026-09-24 13:07 UTC313
        .track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
31ce2453c82fatagen2026-09-24 13:07 UTC314
        .build()
31ce2453c82fatagen2026-09-24 13:07 UTC315
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC316
}
31ce2453c82fatagen2026-09-24 13:07 UTC317
31ce2453c82fatagen2026-09-24 13:07 UTC318
async fn watch_typing(
31ce2453c82fatagen2026-09-24 13:07 UTC319
    room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC320
    mut typing: tokio::sync::broadcast::Receiver<Vec<ruma::OwnedUserId>>,
31ce2453c82fatagen2026-09-24 13:07 UTC321
    events: crate::transport::EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC322
) {
31ce2453c82fatagen2026-09-24 13:07 UTC323
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC324
        match typing.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC325
            Ok(users) => {
31ce2453c82fatagen2026-09-24 13:07 UTC326
                let state = TypingState {
31ce2453c82fatagen2026-09-24 13:07 UTC327
                    room_id: room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC328
                    users,
31ce2453c82fatagen2026-09-24 13:07 UTC329
                };
31ce2453c82fatagen2026-09-24 13:07 UTC330
                if events.send(Event::TypingChanged(state)).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC331
                    return;
31ce2453c82fatagen2026-09-24 13:07 UTC332
                }
31ce2453c82fatagen2026-09-24 13:07 UTC333
            }
31ce2453c82fatagen2026-09-24 13:07 UTC334
            Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC335
                tracing::warn!(%room_id, skipped, "typing watcher lagged");
31ce2453c82fatagen2026-09-24 13:07 UTC336
            }
31ce2453c82fatagen2026-09-24 13:07 UTC337
            Err(tokio::sync::broadcast::error::RecvError::Closed) => return,
31ce2453c82fatagen2026-09-24 13:07 UTC338
        }
31ce2453c82fatagen2026-09-24 13:07 UTC339
    }
31ce2453c82fatagen2026-09-24 13:07 UTC340
}
31ce2453c82fatagen2026-09-24 13:07 UTC341
31ce2453c82fatagen2026-09-24 13:07 UTC342
struct Timelines {
31ce2453c82fatagen2026-09-24 13:07 UTC343
    room: TimelineSession,
31ce2453c82fatagen2026-09-24 13:07 UTC344
    threads: Threads,
1fe480c16a3catagen2026-09-26 08:44 UTC345
    work: TaskSet<()>,
31ce2453c82fatagen2026-09-24 13:07 UTC346
}
31ce2453c82fatagen2026-09-24 13:07 UTC347
31ce2453c82fatagen2026-09-24 13:07 UTC348
impl Timelines {
31ce2453c82fatagen2026-09-24 13:07 UTC349
    async fn execute(&mut self, command: TimelineCommand, reply: Responder, room: &Room) {
31ce2453c82fatagen2026-09-24 13:07 UTC350
        match command {
fa5b4e09943aatagen2026-10-24 09:33 UTC351
            TimelineCommand::OpenThread {
fa5b4e09943aatagen2026-10-24 09:33 UTC352
                room_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC353
                root_event_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC354
            } => self
fa5b4e09943aatagen2026-10-24 09:33 UTC355
                .threads
fa5b4e09943aatagen2026-10-24 09:33 UTC356
                .open(TimelineId::thread(room_id, root_event_id), reply, room),
fa5b4e09943aatagen2026-10-24 09:33 UTC357
            TimelineCommand::CloseThread {
fa5b4e09943aatagen2026-10-24 09:33 UTC358
                room_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC359
                root_event_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC360
            } => {
fa5b4e09943aatagen2026-10-24 09:33 UTC361
                let id = TimelineId::thread(room_id, root_event_id);
fa5b4e09943aatagen2026-10-24 09:33 UTC362
                reply.done(self.threads.close_one(&id).await);
31ce2453c82fatagen2026-09-24 13:07 UTC363
            }
fa5b4e09943aatagen2026-10-24 09:33 UTC364
            TimelineCommand::OpenPinned(room_id) => {
fa5b4e09943aatagen2026-10-24 09:33 UTC365
                self.threads.open(TimelineId::Pinned(room_id), reply, room);
fa5b4e09943aatagen2026-10-24 09:33 UTC366
            }
fa5b4e09943aatagen2026-10-24 09:33 UTC367
            TimelineCommand::ClosePinned(room_id) => {
fa5b4e09943aatagen2026-10-24 09:33 UTC368
                reply.done(self.threads.close_one(&TimelineId::Pinned(room_id)).await);
31ce2453c82fatagen2026-09-24 13:07 UTC369
            }
31ce2453c82fatagen2026-09-24 13:07 UTC370
            TimelineCommand::Resync {
31ce2453c82fatagen2026-09-24 13:07 UTC371
                timeline_id: TimelineId::Room(_),
31ce2453c82fatagen2026-09-24 13:07 UTC372
            } => reply.done(self.room.resync().await),
31ce2453c82fatagen2026-09-24 13:07 UTC373
            command => {
fa5b4e09943aatagen2026-10-24 09:33 UTC374
                let Some(id) = command
31ce2453c82fatagen2026-09-24 13:07 UTC375
                    .timeline_id()
fa5b4e09943aatagen2026-10-24 09:33 UTC376
                    .filter(|id| !matches!(id, TimelineId::Room(_)))
fa5b4e09943aatagen2026-10-24 09:33 UTC377
                    .cloned()
fa5b4e09943aatagen2026-10-24 09:33 UTC378
                else {
31ce2453c82fatagen2026-09-24 13:07 UTC379
                    return self.room.execute(command, reply, &mut self.work).await;
31ce2453c82fatagen2026-09-24 13:07 UTC380
                };
fa5b4e09943aatagen2026-10-24 09:33 UTC381
                if matches!(id, TimelineId::Pinned(_))
fa5b4e09943aatagen2026-10-24 09:33 UTC382
                    && matches!(
fa5b4e09943aatagen2026-10-24 09:33 UTC383
                        command,
fa5b4e09943aatagen2026-10-24 09:33 UTC384
                        TimelineCommand::Send { .. } | TimelineCommand::Paginate { .. }
fa5b4e09943aatagen2026-10-24 09:33 UTC385
                    )
fa5b4e09943aatagen2026-10-24 09:33 UTC386
                {
fa5b4e09943aatagen2026-10-24 09:33 UTC387
                    return reply.done(Err(Error::new(
fa5b4e09943aatagen2026-10-24 09:33 UTC388
                        ErrorKind::Invalid,
fa5b4e09943aatagen2026-10-24 09:33 UTC389
                        "the pinned timeline can't send or paginate",
fa5b4e09943aatagen2026-10-24 09:33 UTC390
                    )));
fa5b4e09943aatagen2026-10-24 09:33 UTC391
                }
5a52012c9b1datagen2026-09-24 23:45 UTC392
                if let TimelineCommand::MarkRead {
5a52012c9b1datagen2026-09-24 23:45 UTC393
                    timeline_id,
5a52012c9b1datagen2026-09-24 23:45 UTC394
                    event_id,
5a52012c9b1datagen2026-09-24 23:45 UTC395
                } = &command
fa5b4e09943aatagen2026-10-24 09:33 UTC396
                    && !self.threads.open.contains_key(&id)
5a52012c9b1datagen2026-09-24 23:45 UTC397
                {
5a52012c9b1datagen2026-09-24 23:45 UTC398
                    let client = room.client();
5a52012c9b1datagen2026-09-24 23:45 UTC399
                    let (timeline_id, event_id) = (timeline_id.clone(), event_id.clone());
5a52012c9b1datagen2026-09-24 23:45 UTC400
                    self.work.spawn(async move {
5a52012c9b1datagen2026-09-24 23:45 UTC401
                        reply.done(
5a52012c9b1datagen2026-09-24 23:45 UTC402
                            super::commands::mark_read(&client, &timeline_id, event_id).await,
5a52012c9b1datagen2026-09-24 23:45 UTC403
                        );
5a52012c9b1datagen2026-09-24 23:45 UTC404
                    });
5a52012c9b1datagen2026-09-24 23:45 UTC405
                    return;
5a52012c9b1datagen2026-09-24 23:45 UTC406
                }
fa5b4e09943aatagen2026-10-24 09:33 UTC407
                match self.threads.route(id, command, reply) {
31ce2453c82fatagen2026-09-24 13:07 UTC408
                    Routed::Open(session, TimelineCommand::Resync { .. }, reply) => {
31ce2453c82fatagen2026-09-24 13:07 UTC409
                        reply.done(session.resync().await);
31ce2453c82fatagen2026-09-24 13:07 UTC410
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC411
                    Routed::Open(session, command, reply) => {
31ce2453c82fatagen2026-09-24 13:07 UTC412
                        session.execute(command, reply, &mut self.work).await;
31ce2453c82fatagen2026-09-24 13:07 UTC413
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC414
                    Routed::Queued => {}
31ce2453c82fatagen2026-09-24 13:07 UTC415
                }
31ce2453c82fatagen2026-09-24 13:07 UTC416
            }
31ce2453c82fatagen2026-09-24 13:07 UTC417
        }
31ce2453c82fatagen2026-09-24 13:07 UTC418
    }
31ce2453c82fatagen2026-09-24 13:07 UTC419
31ce2453c82fatagen2026-09-24 13:07 UTC420
    async fn thread_built(
31ce2453c82fatagen2026-09-24 13:07 UTC421
        &mut self,
1fe480c16a3catagen2026-09-26 08:44 UTC422
        built: Result<Built, JoinError>,
31ce2453c82fatagen2026-09-24 13:07 UTC423
        room_id: &OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC424
        context: &SessionContext,
31ce2453c82fatagen2026-09-24 13:07 UTC425
    ) {
fa5b4e09943aatagen2026-10-24 09:33 UTC426
        let Some((id, queued, timeline)) = self.threads.built(built) else {
31ce2453c82fatagen2026-09-24 13:07 UTC427
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC428
        };
31ce2453c82fatagen2026-09-24 13:07 UTC429
        let timeline = match timeline {
31ce2453c82fatagen2026-09-24 13:07 UTC430
            Ok(timeline) => timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC431
            Err(error) => {
fa5b4e09943aatagen2026-10-24 09:33 UTC432
                tracing::warn!(%error, %room_id, ?id, "timeline build failed");
fa5b4e09943aatagen2026-10-24 09:33 UTC433
                let error = Error::with_source(ErrorKind::Timeline, "timeline build failed", error);
31ce2453c82fatagen2026-09-24 13:07 UTC434
                for (_, reply) in queued {
31ce2453c82fatagen2026-09-24 13:07 UTC435
                    reply.done(Err(error.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC436
                }
31ce2453c82fatagen2026-09-24 13:07 UTC437
                return;
31ce2453c82fatagen2026-09-24 13:07 UTC438
            }
31ce2453c82fatagen2026-09-24 13:07 UTC439
        };
fa5b4e09943aatagen2026-10-24 09:33 UTC440
        let Ok(session) = TimelineSession::start(id.clone(), timeline, context.clone()).await
fa5b4e09943aatagen2026-10-24 09:33 UTC441
        else {
31ce2453c82fatagen2026-09-24 13:07 UTC442
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC443
        };
fa5b4e09943aatagen2026-10-24 09:33 UTC444
        let session = self.threads.open.entry(id).insert_entry(session).into_mut();
31ce2453c82fatagen2026-09-24 13:07 UTC445
        for (command, reply) in queued {
31ce2453c82fatagen2026-09-24 13:07 UTC446
            match command {
31ce2453c82fatagen2026-09-24 13:07 UTC447
                None => reply.done(Ok(())),
31ce2453c82fatagen2026-09-24 13:07 UTC448
                Some(TimelineCommand::Resync { .. }) => reply.done(session.resync().await),
31ce2453c82fatagen2026-09-24 13:07 UTC449
                Some(command) => session.execute(command, reply, &mut self.work).await,
31ce2453c82fatagen2026-09-24 13:07 UTC450
            }
31ce2453c82fatagen2026-09-24 13:07 UTC451
        }
31ce2453c82fatagen2026-09-24 13:07 UTC452
    }
31ce2453c82fatagen2026-09-24 13:07 UTC453
}
31ce2453c82fatagen2026-09-24 13:07 UTC454
31ce2453c82fatagen2026-09-24 13:07 UTC455
type Built = (
fa5b4e09943aatagen2026-10-24 09:33 UTC456
    TimelineId,
31ce2453c82fatagen2026-09-24 13:07 UTC457
    u64,
31ce2453c82fatagen2026-09-24 13:07 UTC458
    Result<Timeline, matrix_sdk_ui::timeline::Error>,
31ce2453c82fatagen2026-09-24 13:07 UTC459
);
31ce2453c82fatagen2026-09-24 13:07 UTC460
31ce2453c82fatagen2026-09-24 13:07 UTC461
/// `None` is the open itself, everything else waits behind it
31ce2453c82fatagen2026-09-24 13:07 UTC462
type Queued = VecDeque<(Option<TimelineCommand>, Responder)>;
31ce2453c82fatagen2026-09-24 13:07 UTC463
31ce2453c82fatagen2026-09-24 13:07 UTC464
enum Routed<'a> {
31ce2453c82fatagen2026-09-24 13:07 UTC465
    Open(&'a mut TimelineSession, TimelineCommand, Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC466
    Queued,
31ce2453c82fatagen2026-09-24 13:07 UTC467
}
31ce2453c82fatagen2026-09-24 13:07 UTC468
31ce2453c82fatagen2026-09-24 13:07 UTC469
struct OpeningThread {
31ce2453c82fatagen2026-09-24 13:07 UTC470
    generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC471
    build: AbortHandle,
31ce2453c82fatagen2026-09-24 13:07 UTC472
    queued: Queued,
31ce2453c82fatagen2026-09-24 13:07 UTC473
}
31ce2453c82fatagen2026-09-24 13:07 UTC474
fa5b4e09943aatagen2026-10-24 09:33 UTC475
/// threads and the pinned timeline, which build off the actor and queue what arrives meanwhile
31ce2453c82fatagen2026-09-24 13:07 UTC476
struct Threads {
fa5b4e09943aatagen2026-10-24 09:33 UTC477
    open: HashMap<TimelineId, TimelineSession>,
fa5b4e09943aatagen2026-10-24 09:33 UTC478
    opening: HashMap<TimelineId, OpeningThread>,
1fe480c16a3catagen2026-09-26 08:44 UTC479
    builds: TaskSet<Built>,
31ce2453c82fatagen2026-09-24 13:07 UTC480
    next_generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC481
    queued: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC482
    capacity: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC483
}
31ce2453c82fatagen2026-09-24 13:07 UTC484
31ce2453c82fatagen2026-09-24 13:07 UTC485
impl Threads {
31ce2453c82fatagen2026-09-24 13:07 UTC486
    fn new(capacity: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC487
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC488
            open: HashMap::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC489
            opening: HashMap::new(),
1fe480c16a3catagen2026-09-26 08:44 UTC490
            builds: TaskSet::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC491
            next_generation: 0,
31ce2453c82fatagen2026-09-24 13:07 UTC492
            queued: 0,
31ce2453c82fatagen2026-09-24 13:07 UTC493
            capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC494
        }
31ce2453c82fatagen2026-09-24 13:07 UTC495
    }
31ce2453c82fatagen2026-09-24 13:07 UTC496
fa5b4e09943aatagen2026-10-24 09:33 UTC497
    fn enqueue(&mut self, id: &TimelineId, command: Option<TimelineCommand>, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC498
        if self.queued >= self.capacity {
31ce2453c82fatagen2026-09-24 13:07 UTC499
            return reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC500
                ErrorKind::QueueFull,
31ce2453c82fatagen2026-09-24 13:07 UTC501
                "thread command queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC502
            )));
31ce2453c82fatagen2026-09-24 13:07 UTC503
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC504
        let Some(opening) = self.opening.get_mut(id) else {
31ce2453c82fatagen2026-09-24 13:07 UTC505
            return reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC506
                ErrorKind::TimelineNotOpen,
fa5b4e09943aatagen2026-10-24 09:33 UTC507
                "timeline is not open",
31ce2453c82fatagen2026-09-24 13:07 UTC508
            )));
31ce2453c82fatagen2026-09-24 13:07 UTC509
        };
31ce2453c82fatagen2026-09-24 13:07 UTC510
        opening.queued.push_back((command, reply));
31ce2453c82fatagen2026-09-24 13:07 UTC511
        self.queued += 1;
31ce2453c82fatagen2026-09-24 13:07 UTC512
    }
31ce2453c82fatagen2026-09-24 13:07 UTC513
fa5b4e09943aatagen2026-10-24 09:33 UTC514
    fn open(&mut self, id: TimelineId, reply: Responder, room: &Room) {
fa5b4e09943aatagen2026-10-24 09:33 UTC515
        if self.open.contains_key(&id) {
31ce2453c82fatagen2026-09-24 13:07 UTC516
            return reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC517
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC518
        if !self.opening.contains_key(&id) {
31ce2453c82fatagen2026-09-24 13:07 UTC519
            let generation = self.next_generation;
31ce2453c82fatagen2026-09-24 13:07 UTC520
            self.next_generation = self.next_generation.wrapping_add(1);
31ce2453c82fatagen2026-09-24 13:07 UTC521
            let room = room.clone();
fa5b4e09943aatagen2026-10-24 09:33 UTC522
            let build_id = id.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC523
            let build = self.builds.spawn(async move {
fa5b4e09943aatagen2026-10-24 09:33 UTC524
                let focus = match &build_id {
fa5b4e09943aatagen2026-10-24 09:33 UTC525
                    TimelineId::Thread { root_event_id, .. } => TimelineFocus::Thread {
fa5b4e09943aatagen2026-10-24 09:33 UTC526
                        root_event_id: root_event_id.clone(),
fa5b4e09943aatagen2026-10-24 09:33 UTC527
                    },
fa5b4e09943aatagen2026-10-24 09:33 UTC528
                    _ => TimelineFocus::PinnedEvents,
31ce2453c82fatagen2026-09-24 13:07 UTC529
                };
fa5b4e09943aatagen2026-10-24 09:33 UTC530
                (build_id, generation, build_timeline(&room, focus).await)
31ce2453c82fatagen2026-09-24 13:07 UTC531
            });
31ce2453c82fatagen2026-09-24 13:07 UTC532
            self.opening.insert(
fa5b4e09943aatagen2026-10-24 09:33 UTC533
                id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC534
                OpeningThread {
31ce2453c82fatagen2026-09-24 13:07 UTC535
                    generation,
31ce2453c82fatagen2026-09-24 13:07 UTC536
                    build,
31ce2453c82fatagen2026-09-24 13:07 UTC537
                    queued: VecDeque::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC538
                },
31ce2453c82fatagen2026-09-24 13:07 UTC539
            );
31ce2453c82fatagen2026-09-24 13:07 UTC540
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC541
        self.enqueue(&id, None, reply);
31ce2453c82fatagen2026-09-24 13:07 UTC542
    }
31ce2453c82fatagen2026-09-24 13:07 UTC543
fa5b4e09943aatagen2026-10-24 09:33 UTC544
    fn route(&mut self, id: TimelineId, command: TimelineCommand, reply: Responder) -> Routed<'_> {
fa5b4e09943aatagen2026-10-24 09:33 UTC545
        if !self.open.contains_key(&id) {
fa5b4e09943aatagen2026-10-24 09:33 UTC546
            self.enqueue(&id, Some(command), reply);
31ce2453c82fatagen2026-09-24 13:07 UTC547
            return Routed::Queued;
31ce2453c82fatagen2026-09-24 13:07 UTC548
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC549
        let session = self.open.get_mut(&id).expect("timeline was just checked");
31ce2453c82fatagen2026-09-24 13:07 UTC550
        Routed::Open(session, command, reply)
31ce2453c82fatagen2026-09-24 13:07 UTC551
    }
31ce2453c82fatagen2026-09-24 13:07 UTC552
31ce2453c82fatagen2026-09-24 13:07 UTC553
    /// `None` when the build belongs to a thread that was closed while it ran
31ce2453c82fatagen2026-09-24 13:07 UTC554
    fn built(
31ce2453c82fatagen2026-09-24 13:07 UTC555
        &mut self,
1fe480c16a3catagen2026-09-26 08:44 UTC556
        built: Result<Built, JoinError>,
31ce2453c82fatagen2026-09-24 13:07 UTC557
    ) -> Option<(
fa5b4e09943aatagen2026-10-24 09:33 UTC558
        TimelineId,
31ce2453c82fatagen2026-09-24 13:07 UTC559
        Queued,
31ce2453c82fatagen2026-09-24 13:07 UTC560
        Result<Timeline, matrix_sdk_ui::timeline::Error>,
31ce2453c82fatagen2026-09-24 13:07 UTC561
    )> {
fa5b4e09943aatagen2026-10-24 09:33 UTC562
        let (id, generation, timeline) = match built {
31ce2453c82fatagen2026-09-24 13:07 UTC563
            Ok(built) => built,
31ce2453c82fatagen2026-09-24 13:07 UTC564
            Err(error) if error.is_cancelled() => return None,
1fe480c16a3catagen2026-09-26 08:44 UTC565
            Err(error) => resume_unwind(error),
31ce2453c82fatagen2026-09-24 13:07 UTC566
        };
31ce2453c82fatagen2026-09-24 13:07 UTC567
        if self
31ce2453c82fatagen2026-09-24 13:07 UTC568
            .opening
fa5b4e09943aatagen2026-10-24 09:33 UTC569
            .get(&id)
31ce2453c82fatagen2026-09-24 13:07 UTC570
            .is_none_or(|opening| opening.generation != generation)
31ce2453c82fatagen2026-09-24 13:07 UTC571
        {
31ce2453c82fatagen2026-09-24 13:07 UTC572
            return None;
31ce2453c82fatagen2026-09-24 13:07 UTC573
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC574
        let opening = self.opening.remove(&id)?;
31ce2453c82fatagen2026-09-24 13:07 UTC575
        self.queued -= opening.queued.len();
fa5b4e09943aatagen2026-10-24 09:33 UTC576
        Some((id, opening.queued, timeline))
31ce2453c82fatagen2026-09-24 13:07 UTC577
    }
31ce2453c82fatagen2026-09-24 13:07 UTC578
fa5b4e09943aatagen2026-10-24 09:33 UTC579
    async fn close_one(&mut self, id: &TimelineId) -> Result<(), Error> {
fa5b4e09943aatagen2026-10-24 09:33 UTC580
        if let Some(session) = self.open.remove(id) {
31ce2453c82fatagen2026-09-24 13:07 UTC581
            session.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC582
            return Ok(());
31ce2453c82fatagen2026-09-24 13:07 UTC583
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC584
        let Some(opening) = self.opening.remove(id) else {
31ce2453c82fatagen2026-09-24 13:07 UTC585
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC586
                ErrorKind::TimelineNotOpen,
fa5b4e09943aatagen2026-10-24 09:33 UTC587
                "timeline is not open",
31ce2453c82fatagen2026-09-24 13:07 UTC588
            ));
31ce2453c82fatagen2026-09-24 13:07 UTC589
        };
31ce2453c82fatagen2026-09-24 13:07 UTC590
        opening.build.abort();
31ce2453c82fatagen2026-09-24 13:07 UTC591
        self.queued -= opening.queued.len();
31ce2453c82fatagen2026-09-24 13:07 UTC592
        Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC593
    }
31ce2453c82fatagen2026-09-24 13:07 UTC594
31ce2453c82fatagen2026-09-24 13:07 UTC595
    async fn close(&mut self) {
1fe480c16a3catagen2026-09-26 08:44 UTC596
        self.builds.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC597
        self.opening.clear();
31ce2453c82fatagen2026-09-24 13:07 UTC598
        for (_, session) in self.open.drain() {
31ce2453c82fatagen2026-09-24 13:07 UTC599
            session.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC600
        }
31ce2453c82fatagen2026-09-24 13:07 UTC601
    }
31ce2453c82fatagen2026-09-24 13:07 UTC602
}