atagen

macltok

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

Blame: src/engine/mod.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
mod commands;
a319bae745d8atagen2026-09-26 09:59 UTC2
pub(crate) mod executor;
31ce2453c82fatagen2026-09-24 13:07 UTC3
mod media;
31ce2453c82fatagen2026-09-24 13:07 UTC4
mod mutations;
38c4caecdb3datagen2026-09-26 13:35 UTC5
mod prepare;
31ce2453c82fatagen2026-09-24 13:07 UTC6
mod room;
f12377bce9b9atagen2026-09-26 12:32 UTC7
mod session;
31ce2453c82fatagen2026-09-24 13:07 UTC8
mod timeline;
31ce2453c82fatagen2026-09-24 13:07 UTC9
mod watchers;
31ce2453c82fatagen2026-09-24 13:07 UTC10
31ce2453c82fatagen2026-09-24 13:07 UTC11
use std::{
31ce2453c82fatagen2026-09-24 13:07 UTC12
    collections::{HashMap, VecDeque},
31ce2453c82fatagen2026-09-24 13:07 UTC13
    future::Future,
1fe480c16a3catagen2026-09-26 08:44 UTC14
    panic::AssertUnwindSafe,
31ce2453c82fatagen2026-09-24 13:07 UTC15
    sync::Arc,
31ce2453c82fatagen2026-09-24 13:07 UTC16
};
31ce2453c82fatagen2026-09-24 13:07 UTC17
1fe480c16a3catagen2026-09-26 08:44 UTC18
use futures_util::FutureExt;
1fe480c16a3catagen2026-09-26 08:44 UTC19
use matrix_sdk::{Client, SendOutsideWasm, SessionChange};
31ce2453c82fatagen2026-09-24 13:07 UTC20
use matrix_sdk_ui::sync_service::SyncService;
673563a76e43atagen2026-09-26 12:30 UTC21
use ruma::{OwnedDeviceId, OwnedRoomId};
31ce2453c82fatagen2026-09-24 13:07 UTC22
use thiserror::Error as ThisError;
fe4133dbcd47atagen2026-09-26 09:05 UTC23
use tokio::sync::{broadcast, mpsc, watch};
31ce2453c82fatagen2026-09-24 13:07 UTC24
31ce2453c82fatagen2026-09-24 13:07 UTC25
pub(crate) use media::MediaService;
f12377bce9b9atagen2026-09-26 12:32 UTC26
pub(crate) use session::Persistence;
31ce2453c82fatagen2026-09-24 13:07 UTC27
a250c7fa43fbatagen2026-09-26 13:35 UTC28
#[cfg(any(feature = "sqlite", feature = "indexeddb"))]
a250c7fa43fbatagen2026-09-26 13:35 UTC29
pub(crate) use crate::store_lock::StoreLock;
a250c7fa43fbatagen2026-09-26 13:35 UTC30
#[cfg(not(any(feature = "sqlite", feature = "indexeddb")))]
a250c7fa43fbatagen2026-09-26 13:35 UTC31
pub(crate) enum StoreLock {}
a250c7fa43fbatagen2026-09-26 13:35 UTC32
31ce2453c82fatagen2026-09-24 13:07 UTC33
use self::{
31ce2453c82fatagen2026-09-24 13:07 UTC34
    commands::Job,
1fe480c16a3catagen2026-09-26 08:44 UTC35
    executor::{EngineBoxFuture, TaskSet},
31ce2453c82fatagen2026-09-24 13:07 UTC36
    mutations::{Mutation, MutationJob},
5a52012c9b1datagen2026-09-24 23:45 UTC37
    room::{ActorNote, ActorRequest, RoomActor},
31ce2453c82fatagen2026-09-24 13:07 UTC38
    timeline::{SessionContext, Supervisor},
31ce2453c82fatagen2026-09-24 13:07 UTC39
};
31ce2453c82fatagen2026-09-24 13:07 UTC40
use crate::{
31ce2453c82fatagen2026-09-24 13:07 UTC41
    AccountCommand, ClientHandle, Command, ConnectionState, EncryptionCommand, EngineErrorKind,
31ce2453c82fatagen2026-09-24 13:07 UTC42
    EngineFailure, Error, ErrorKind, Event, EventStream, LifecycleCommand, MediaCommand,
fe4133dbcd47atagen2026-09-26 09:05 UTC43
    NotificationCommand, RoomCommand, Termination, TimelineCommand,
17e042fbc331atagen2026-10-08 12:39 UTC44
    normalise::ROOM_PACKS,
31ce2453c82fatagen2026-09-24 13:07 UTC45
    transport::{DriverPort, Request, Responder, transport_pair},
31ce2453c82fatagen2026-09-24 13:07 UTC46
};
31ce2453c82fatagen2026-09-24 13:07 UTC47
31ce2453c82fatagen2026-09-24 13:07 UTC48
#[derive(Clone, Debug, Eq, PartialEq)]
31ce2453c82fatagen2026-09-24 13:07 UTC49
#[non_exhaustive]
31ce2453c82fatagen2026-09-24 13:07 UTC50
pub struct EngineConfig {
31ce2453c82fatagen2026-09-24 13:07 UTC51
    pub command_capacity: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC52
    pub event_capacity: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC53
    pub initial_history_limit: u16,
31ce2453c82fatagen2026-09-24 13:07 UTC54
    pub max_media_bytes: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC55
    pub max_media_sources: usize,
38c4caecdb3datagen2026-09-26 13:35 UTC56
    /// warm a room up for sending when it's opened or someone starts typing in it
38c4caecdb3datagen2026-09-26 13:35 UTC57
    pub prepare_to_send: bool,
31ce2453c82fatagen2026-09-24 13:07 UTC58
}
31ce2453c82fatagen2026-09-24 13:07 UTC59
31ce2453c82fatagen2026-09-24 13:07 UTC60
impl EngineConfig {
31ce2453c82fatagen2026-09-24 13:07 UTC61
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC62
    pub const fn with_command_capacity(mut self, capacity: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC63
        self.command_capacity = capacity;
31ce2453c82fatagen2026-09-24 13:07 UTC64
        self
31ce2453c82fatagen2026-09-24 13:07 UTC65
    }
31ce2453c82fatagen2026-09-24 13:07 UTC66
31ce2453c82fatagen2026-09-24 13:07 UTC67
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC68
    pub const fn with_event_capacity(mut self, capacity: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC69
        self.event_capacity = capacity;
31ce2453c82fatagen2026-09-24 13:07 UTC70
        self
31ce2453c82fatagen2026-09-24 13:07 UTC71
    }
31ce2453c82fatagen2026-09-24 13:07 UTC72
31ce2453c82fatagen2026-09-24 13:07 UTC73
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC74
    pub const fn with_initial_history_limit(mut self, limit: u16) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC75
        self.initial_history_limit = limit;
31ce2453c82fatagen2026-09-24 13:07 UTC76
        self
31ce2453c82fatagen2026-09-24 13:07 UTC77
    }
31ce2453c82fatagen2026-09-24 13:07 UTC78
31ce2453c82fatagen2026-09-24 13:07 UTC79
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC80
    pub const fn with_max_media_bytes(mut self, bytes: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC81
        self.max_media_bytes = bytes;
31ce2453c82fatagen2026-09-24 13:07 UTC82
        self
31ce2453c82fatagen2026-09-24 13:07 UTC83
    }
31ce2453c82fatagen2026-09-24 13:07 UTC84
31ce2453c82fatagen2026-09-24 13:07 UTC85
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC86
    pub const fn with_max_media_sources(mut self, sources: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC87
        self.max_media_sources = sources;
31ce2453c82fatagen2026-09-24 13:07 UTC88
        self
31ce2453c82fatagen2026-09-24 13:07 UTC89
    }
38c4caecdb3datagen2026-09-26 13:35 UTC90
38c4caecdb3datagen2026-09-26 13:35 UTC91
    #[must_use]
38c4caecdb3datagen2026-09-26 13:35 UTC92
    pub const fn with_prepare_to_send(mut self, prepare: bool) -> Self {
38c4caecdb3datagen2026-09-26 13:35 UTC93
        self.prepare_to_send = prepare;
38c4caecdb3datagen2026-09-26 13:35 UTC94
        self
38c4caecdb3datagen2026-09-26 13:35 UTC95
    }
31ce2453c82fatagen2026-09-24 13:07 UTC96
}
31ce2453c82fatagen2026-09-24 13:07 UTC97
31ce2453c82fatagen2026-09-24 13:07 UTC98
impl Default for EngineConfig {
31ce2453c82fatagen2026-09-24 13:07 UTC99
    fn default() -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC100
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC101
            command_capacity: 64,
31ce2453c82fatagen2026-09-24 13:07 UTC102
            event_capacity: 128,
31ce2453c82fatagen2026-09-24 13:07 UTC103
            initial_history_limit: 30,
31ce2453c82fatagen2026-09-24 13:07 UTC104
            max_media_bytes: 20 * 1024 * 1024,
31ce2453c82fatagen2026-09-24 13:07 UTC105
            max_media_sources: 2_048,
38c4caecdb3datagen2026-09-26 13:35 UTC106
            prepare_to_send: true,
31ce2453c82fatagen2026-09-24 13:07 UTC107
        }
31ce2453c82fatagen2026-09-24 13:07 UTC108
    }
31ce2453c82fatagen2026-09-24 13:07 UTC109
}
31ce2453c82fatagen2026-09-24 13:07 UTC110
31ce2453c82fatagen2026-09-24 13:07 UTC111
#[derive(Debug, ThisError)]
31ce2453c82fatagen2026-09-24 13:07 UTC112
pub enum EngineError {
31ce2453c82fatagen2026-09-24 13:07 UTC113
    #[error("the Matrix client is not authenticated")]
31ce2453c82fatagen2026-09-24 13:07 UTC114
    Unauthenticated,
31ce2453c82fatagen2026-09-24 13:07 UTC115
    #[error("command, event, and media source capacities must be nonzero")]
31ce2453c82fatagen2026-09-24 13:07 UTC116
    ZeroCapacity,
31ce2453c82fatagen2026-09-24 13:07 UTC117
    #[error("could not build the sync service")]
31ce2453c82fatagen2026-09-24 13:07 UTC118
    SyncService(#[source] matrix_sdk_ui::sync_service::Error),
31ce2453c82fatagen2026-09-24 13:07 UTC119
    #[error("the event receiver has closed")]
31ce2453c82fatagen2026-09-24 13:07 UTC120
    EventReceiverClosed,
31ce2453c82fatagen2026-09-24 13:07 UTC121
    #[error("engine task {task} panicked")]
31ce2453c82fatagen2026-09-24 13:07 UTC122
    TaskPanicked { task: String },
31ce2453c82fatagen2026-09-24 13:07 UTC123
}
31ce2453c82fatagen2026-09-24 13:07 UTC124
31ce2453c82fatagen2026-09-24 13:07 UTC125
impl EngineError {
31ce2453c82fatagen2026-09-24 13:07 UTC126
    fn failure(&self) -> EngineFailure {
31ce2453c82fatagen2026-09-24 13:07 UTC127
        EngineFailure {
31ce2453c82fatagen2026-09-24 13:07 UTC128
            kind: match self {
31ce2453c82fatagen2026-09-24 13:07 UTC129
                Self::EventReceiverClosed => EngineErrorKind::EventReceiverClosed,
31ce2453c82fatagen2026-09-24 13:07 UTC130
                Self::TaskPanicked { .. } => EngineErrorKind::Watcher,
31ce2453c82fatagen2026-09-24 13:07 UTC131
                Self::Unauthenticated | Self::ZeroCapacity | Self::SyncService(_) => {
31ce2453c82fatagen2026-09-24 13:07 UTC132
                    EngineErrorKind::Other
31ce2453c82fatagen2026-09-24 13:07 UTC133
                }
31ce2453c82fatagen2026-09-24 13:07 UTC134
            },
31ce2453c82fatagen2026-09-24 13:07 UTC135
            message: self.to_string(),
31ce2453c82fatagen2026-09-24 13:07 UTC136
        }
31ce2453c82fatagen2026-09-24 13:07 UTC137
    }
31ce2453c82fatagen2026-09-24 13:07 UTC138
}
31ce2453c82fatagen2026-09-24 13:07 UTC139
31ce2453c82fatagen2026-09-24 13:07 UTC140
#[derive(Clone)]
31ce2453c82fatagen2026-09-24 13:07 UTC141
enum TaskContext {
31ce2453c82fatagen2026-09-24 13:07 UTC142
    /// a panic here costs the caller an `Interrupted` answer and nothing else
31ce2453c82fatagen2026-09-24 13:07 UTC143
    Command(&'static str),
31ce2453c82fatagen2026-09-24 13:07 UTC144
    /// a panic here takes the engine with it
31ce2453c82fatagen2026-09-24 13:07 UTC145
    Watcher(&'static str),
31ce2453c82fatagen2026-09-24 13:07 UTC146
    RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC147
        room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC148
        generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC149
    },
31ce2453c82fatagen2026-09-24 13:07 UTC150
}
31ce2453c82fatagen2026-09-24 13:07 UTC151
31ce2453c82fatagen2026-09-24 13:07 UTC152
impl TaskContext {
31ce2453c82fatagen2026-09-24 13:07 UTC153
    fn label(&self) -> String {
31ce2453c82fatagen2026-09-24 13:07 UTC154
        match self {
31ce2453c82fatagen2026-09-24 13:07 UTC155
            Self::Command(name) | Self::Watcher(name) => (*name).to_owned(),
31ce2453c82fatagen2026-09-24 13:07 UTC156
            Self::RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC157
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC158
                generation,
31ce2453c82fatagen2026-09-24 13:07 UTC159
            } => format!("room actor {room_id} generation {generation}"),
31ce2453c82fatagen2026-09-24 13:07 UTC160
        }
31ce2453c82fatagen2026-09-24 13:07 UTC161
    }
31ce2453c82fatagen2026-09-24 13:07 UTC162
}
31ce2453c82fatagen2026-09-24 13:07 UTC163
7b376d620a3datagen2026-09-26 12:53 UTC164
/// tasks catch their own panics so each comes back with its context, since joining
7b376d620a3datagen2026-09-26 12:53 UTC165
/// through the sdk's executor gives no task id to look one up by. wasm has nothing to catch
1fe480c16a3catagen2026-09-26 08:44 UTC166
fn panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
1fe480c16a3catagen2026-09-26 08:44 UTC167
    payload
1fe480c16a3catagen2026-09-26 08:44 UTC168
        .downcast_ref::<&str>()
1fe480c16a3catagen2026-09-26 08:44 UTC169
        .map(ToString::to_string)
1fe480c16a3catagen2026-09-26 08:44 UTC170
        .or_else(|| payload.downcast_ref::<String>().cloned())
1fe480c16a3catagen2026-09-26 08:44 UTC171
        .unwrap_or_else(|| "task panicked with a non-string payload".to_owned())
1fe480c16a3catagen2026-09-26 08:44 UTC172
}
1fe480c16a3catagen2026-09-26 08:44 UTC173
31ce2453c82fatagen2026-09-24 13:07 UTC174
struct TaskRegistry {
1fe480c16a3catagen2026-09-26 08:44 UTC175
    tasks: TaskSet<(TaskContext, Option<String>)>,
31ce2453c82fatagen2026-09-24 13:07 UTC176
}
31ce2453c82fatagen2026-09-24 13:07 UTC177
31ce2453c82fatagen2026-09-24 13:07 UTC178
impl TaskRegistry {
31ce2453c82fatagen2026-09-24 13:07 UTC179
    fn spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC180
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC181
        context: TaskContext,
1fe480c16a3catagen2026-09-26 08:44 UTC182
        task: impl Future<Output = ()> + SendOutsideWasm + 'static,
1fe480c16a3catagen2026-09-26 08:44 UTC183
    ) {
1fe480c16a3catagen2026-09-26 08:44 UTC184
        self.tasks.spawn(async move {
1fe480c16a3catagen2026-09-26 08:44 UTC185
            let panic = AssertUnwindSafe(task)
1fe480c16a3catagen2026-09-26 08:44 UTC186
                .catch_unwind()
1fe480c16a3catagen2026-09-26 08:44 UTC187
                .await
1fe480c16a3catagen2026-09-26 08:44 UTC188
                .err()
1fe480c16a3catagen2026-09-26 08:44 UTC189
                .map(panic_message);
1fe480c16a3catagen2026-09-26 08:44 UTC190
            (context, panic)
1fe480c16a3catagen2026-09-26 08:44 UTC191
        });
31ce2453c82fatagen2026-09-24 13:07 UTC192
    }
31ce2453c82fatagen2026-09-24 13:07 UTC193
1fe480c16a3catagen2026-09-26 08:44 UTC194
    fn is_empty(&self) -> bool {
1fe480c16a3catagen2026-09-26 08:44 UTC195
        self.tasks.is_empty()
1fe480c16a3catagen2026-09-26 08:44 UTC196
    }
1fe480c16a3catagen2026-09-26 08:44 UTC197
1fe480c16a3catagen2026-09-26 08:44 UTC198
    async fn join_next(&mut self) -> Option<(TaskContext, Option<String>)> {
1fe480c16a3catagen2026-09-26 08:44 UTC199
        loop {
1fe480c16a3catagen2026-09-26 08:44 UTC200
            if let Ok(done) = self.tasks.join_next().await? {
1fe480c16a3catagen2026-09-26 08:44 UTC201
                return Some(done);
1fe480c16a3catagen2026-09-26 08:44 UTC202
            }
1fe480c16a3catagen2026-09-26 08:44 UTC203
        }
31ce2453c82fatagen2026-09-24 13:07 UTC204
    }
31ce2453c82fatagen2026-09-24 13:07 UTC205
31ce2453c82fatagen2026-09-24 13:07 UTC206
    async fn shutdown(&mut self) {
1fe480c16a3catagen2026-09-26 08:44 UTC207
        self.tasks.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC208
    }
31ce2453c82fatagen2026-09-24 13:07 UTC209
}
31ce2453c82fatagen2026-09-24 13:07 UTC210
31ce2453c82fatagen2026-09-24 13:07 UTC211
enum RoomSlot {
31ce2453c82fatagen2026-09-24 13:07 UTC212
    Active {
31ce2453c82fatagen2026-09-24 13:07 UTC213
        generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC214
        sender: mpsc::Sender<ActorRequest>,
31ce2453c82fatagen2026-09-24 13:07 UTC215
    },
31ce2453c82fatagen2026-09-24 13:07 UTC216
    /// opens and closes that arrive while the actor winds down, replayed in order once it's gone
31ce2453c82fatagen2026-09-24 13:07 UTC217
    Closing {
31ce2453c82fatagen2026-09-24 13:07 UTC218
        generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC219
        pending: VecDeque<Deferred>,
31ce2453c82fatagen2026-09-24 13:07 UTC220
    },
31ce2453c82fatagen2026-09-24 13:07 UTC221
}
31ce2453c82fatagen2026-09-24 13:07 UTC222
31ce2453c82fatagen2026-09-24 13:07 UTC223
impl RoomSlot {
31ce2453c82fatagen2026-09-24 13:07 UTC224
    const fn generation(&self) -> u64 {
31ce2453c82fatagen2026-09-24 13:07 UTC225
        match self {
31ce2453c82fatagen2026-09-24 13:07 UTC226
            Self::Active { generation, .. } | Self::Closing { generation, .. } => *generation,
31ce2453c82fatagen2026-09-24 13:07 UTC227
        }
31ce2453c82fatagen2026-09-24 13:07 UTC228
    }
31ce2453c82fatagen2026-09-24 13:07 UTC229
}
31ce2453c82fatagen2026-09-24 13:07 UTC230
31ce2453c82fatagen2026-09-24 13:07 UTC231
enum Deferred {
31ce2453c82fatagen2026-09-24 13:07 UTC232
    Open(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC233
    Close(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC234
}
31ce2453c82fatagen2026-09-24 13:07 UTC235
673563a76e43atagen2026-09-26 12:30 UTC236
/// logouts in flight, and a token rejection held back because one of them caused it
673563a76e43atagen2026-09-26 12:30 UTC237
#[derive(Default)]
673563a76e43atagen2026-09-26 12:30 UTC238
pub(super) struct Logouts {
673563a76e43atagen2026-09-26 12:30 UTC239
    pub(super) in_flight: usize,
673563a76e43atagen2026-09-26 12:30 UTC240
    pub(super) rejected: Option<Option<OwnedDeviceId>>,
673563a76e43atagen2026-09-26 12:30 UTC241
}
673563a76e43atagen2026-09-26 12:30 UTC242
31ce2453c82fatagen2026-09-24 13:07 UTC243
struct Queues {
31ce2453c82fatagen2026-09-24 13:07 UTC244
    mutations: mpsc::Receiver<MutationJob>,
31ce2453c82fatagen2026-09-24 13:07 UTC245
    notifications: mpsc::Receiver<Job<NotificationCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC246
    encryption: mpsc::Receiver<Job<EncryptionCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC247
    session_changes: broadcast::Receiver<SessionChange>,
f12377bce9b9atagen2026-09-26 12:32 UTC248
    persistence: Option<Persistence>,
31ce2453c82fatagen2026-09-24 13:07 UTC249
}
31ce2453c82fatagen2026-09-24 13:07 UTC250
5a52012c9b1datagen2026-09-24 23:45 UTC251
/// requests other devices send us, handed from the sdk's event handler to the encryption worker
5a52012c9b1datagen2026-09-24 23:45 UTC252
type IncomingVerifications =
5a52012c9b1datagen2026-09-24 23:45 UTC253
    mpsc::UnboundedSender<matrix_sdk::encryption::verification::VerificationRequest>;
5a52012c9b1datagen2026-09-24 23:45 UTC254
31ce2453c82fatagen2026-09-24 13:07 UTC255
pub struct Engine {
31ce2453c82fatagen2026-09-24 13:07 UTC256
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC257
    sync: Arc<SyncService>,
31ce2453c82fatagen2026-09-24 13:07 UTC258
    port: DriverPort,
31ce2453c82fatagen2026-09-24 13:07 UTC259
    config: EngineConfig,
31ce2453c82fatagen2026-09-24 13:07 UTC260
    media: Arc<MediaService>,
31ce2453c82fatagen2026-09-24 13:07 UTC261
    tasks: TaskRegistry,
31ce2453c82fatagen2026-09-24 13:07 UTC262
    rooms: HashMap<OwnedRoomId, RoomSlot>,
31ce2453c82fatagen2026-09-24 13:07 UTC263
    next_generation: u64,
5a52012c9b1datagen2026-09-24 23:45 UTC264
    notes: mpsc::UnboundedReceiver<ActorNote>,
5a52012c9b1datagen2026-09-24 23:45 UTC265
    note_sender: mpsc::UnboundedSender<ActorNote>,
5a52012c9b1datagen2026-09-24 23:45 UTC266
    /// the rooms sliding sync should follow, applied off the run loop since it touches the store
5a52012c9b1datagen2026-09-24 23:45 UTC267
    subscriptions: watch::Sender<Vec<OwnedRoomId>>,
31ce2453c82fatagen2026-09-24 13:07 UTC268
    mutations: mpsc::Sender<MutationJob>,
31ce2453c82fatagen2026-09-24 13:07 UTC269
    notifications: mpsc::Sender<Job<NotificationCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC270
    encryption: mpsc::Sender<Job<EncryptionCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC271
    queues: Option<Queues>,
5a52012c9b1datagen2026-09-24 23:45 UTC272
    /// the client can outlive us, so the handler has to come off it when we stop
5a52012c9b1datagen2026-09-24 23:45 UTC273
    verification_handler: Option<matrix_sdk::event_handler::EventHandlerHandle>,
fe4133dbcd47atagen2026-09-26 09:05 UTC274
    stop: mpsc::UnboundedSender<Termination>,
fe4133dbcd47atagen2026-09-26 09:05 UTC275
    stops: mpsc::UnboundedReceiver<Termination>,
673563a76e43atagen2026-09-26 12:30 UTC276
    logouts: Arc<watch::Sender<Logouts>>,
38c4caecdb3datagen2026-09-26 13:35 UTC277
    preparations: prepare::Preparations,
a250c7fa43fbatagen2026-09-26 13:35 UTC278
    /// shared with every `ClientHandle`, so the store stays locked while the client is reachable
a250c7fa43fbatagen2026-09-26 13:35 UTC279
    _store_lock: Option<Arc<StoreLock>>,
31ce2453c82fatagen2026-09-24 13:07 UTC280
}
31ce2453c82fatagen2026-09-24 13:07 UTC281
31ce2453c82fatagen2026-09-24 13:07 UTC282
impl Engine {
31ce2453c82fatagen2026-09-24 13:07 UTC283
    /// the escape hatch for a client you built yourself. `Session` is the usual way in
31ce2453c82fatagen2026-09-24 13:07 UTC284
    pub async fn from_client(
31ce2453c82fatagen2026-09-24 13:07 UTC285
        client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC286
        config: EngineConfig,
31ce2453c82fatagen2026-09-24 13:07 UTC287
    ) -> Result<(Self, ClientHandle, EventStream), EngineError> {
f12377bce9b9atagen2026-09-26 12:32 UTC288
        let session_changes = client.subscribe_to_session_changes();
a250c7fa43fbatagen2026-09-26 13:35 UTC289
        Self::with_persistence(client, config, session_changes, None, None).await
f12377bce9b9atagen2026-09-26 12:32 UTC290
    }
f12377bce9b9atagen2026-09-26 12:32 UTC291
f12377bce9b9atagen2026-09-26 12:32 UTC292
    /// `session_changes` is taken when the client is built, since the sdk can refresh
f12377bce9b9atagen2026-09-26 12:32 UTC293
    /// before an engine exists
f12377bce9b9atagen2026-09-26 12:32 UTC294
    pub(crate) async fn with_persistence(
f12377bce9b9atagen2026-09-26 12:32 UTC295
        client: Client,
f12377bce9b9atagen2026-09-26 12:32 UTC296
        config: EngineConfig,
f12377bce9b9atagen2026-09-26 12:32 UTC297
        session_changes: broadcast::Receiver<SessionChange>,
f12377bce9b9atagen2026-09-26 12:32 UTC298
        persistence: Option<Persistence>,
a250c7fa43fbatagen2026-09-26 13:35 UTC299
        store_lock: Option<StoreLock>,
f12377bce9b9atagen2026-09-26 12:32 UTC300
    ) -> Result<(Self, ClientHandle, EventStream), EngineError> {
31ce2453c82fatagen2026-09-24 13:07 UTC301
        if client.user_id().is_none() {
31ce2453c82fatagen2026-09-24 13:07 UTC302
            return Err(EngineError::Unauthenticated);
31ce2453c82fatagen2026-09-24 13:07 UTC303
        }
31ce2453c82fatagen2026-09-24 13:07 UTC304
        if config.command_capacity == 0
31ce2453c82fatagen2026-09-24 13:07 UTC305
            || config.event_capacity == 0
31ce2453c82fatagen2026-09-24 13:07 UTC306
            || config.max_media_sources == 0
31ce2453c82fatagen2026-09-24 13:07 UTC307
        {
31ce2453c82fatagen2026-09-24 13:07 UTC308
            return Err(EngineError::ZeroCapacity);
31ce2453c82fatagen2026-09-24 13:07 UTC309
        }
31ce2453c82fatagen2026-09-24 13:07 UTC310
        let sync = Arc::new(
31ce2453c82fatagen2026-09-24 13:07 UTC311
            SyncService::builder(client.clone())
31ce2453c82fatagen2026-09-24 13:07 UTC312
                .with_offline_mode()
17e042fbc331atagen2026-10-08 12:39 UTC313
                .with_room_list_extra_required_state(vec![(ROOM_PACKS.into(), "*".to_owned())])
31ce2453c82fatagen2026-09-24 13:07 UTC314
                .build()
31ce2453c82fatagen2026-09-24 13:07 UTC315
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC316
                .map_err(EngineError::SyncService)?,
31ce2453c82fatagen2026-09-24 13:07 UTC317
        );
a250c7fa43fbatagen2026-09-26 13:35 UTC318
        let store_lock = store_lock.map(Arc::new);
31ce2453c82fatagen2026-09-24 13:07 UTC319
        let media = Arc::new(MediaService::new(
31ce2453c82fatagen2026-09-24 13:07 UTC320
            client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC321
            config.max_media_bytes,
31ce2453c82fatagen2026-09-24 13:07 UTC322
            config.max_media_sources,
31ce2453c82fatagen2026-09-24 13:07 UTC323
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC324
        let (handle, port, events) = transport_pair(
31ce2453c82fatagen2026-09-24 13:07 UTC325
            client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC326
            sync.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC327
            media.clone(),
a250c7fa43fbatagen2026-09-26 13:35 UTC328
            store_lock.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC329
            config.command_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC330
            config.event_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC331
        );
31ce2453c82fatagen2026-09-24 13:07 UTC332
        client.send_queue().enable_upload_progress(true);
411bc1c78b65atagen2026-10-22 07:39 UTC333
        client.send_queue().enable_global_upload_progress(true);
5a52012c9b1datagen2026-09-24 23:45 UTC334
        let (note_sender, notes) = mpsc::unbounded_channel();
fe4133dbcd47atagen2026-09-26 09:05 UTC335
        let (stop, stops) = mpsc::unbounded_channel();
31ce2453c82fatagen2026-09-24 13:07 UTC336
        let (mutations, mutation_queue) = mpsc::channel(config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC337
        let (notifications, notification_queue) = mpsc::channel(config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC338
        let (encryption, encryption_queue) = mpsc::channel(config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC339
        let engine = Self {
31ce2453c82fatagen2026-09-24 13:07 UTC340
            client,
31ce2453c82fatagen2026-09-24 13:07 UTC341
            sync,
31ce2453c82fatagen2026-09-24 13:07 UTC342
            port,
31ce2453c82fatagen2026-09-24 13:07 UTC343
            config,
31ce2453c82fatagen2026-09-24 13:07 UTC344
            media,
31ce2453c82fatagen2026-09-24 13:07 UTC345
            tasks: TaskRegistry {
1fe480c16a3catagen2026-09-26 08:44 UTC346
                tasks: TaskSet::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC347
            },
31ce2453c82fatagen2026-09-24 13:07 UTC348
            rooms: HashMap::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC349
            next_generation: 0,
5a52012c9b1datagen2026-09-24 23:45 UTC350
            notes,
5a52012c9b1datagen2026-09-24 23:45 UTC351
            note_sender,
5a52012c9b1datagen2026-09-24 23:45 UTC352
            subscriptions: watch::Sender::new(Vec::new()),
31ce2453c82fatagen2026-09-24 13:07 UTC353
            mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC354
            notifications,
31ce2453c82fatagen2026-09-24 13:07 UTC355
            encryption,
31ce2453c82fatagen2026-09-24 13:07 UTC356
            queues: Some(Queues {
31ce2453c82fatagen2026-09-24 13:07 UTC357
                mutations: mutation_queue,
31ce2453c82fatagen2026-09-24 13:07 UTC358
                notifications: notification_queue,
31ce2453c82fatagen2026-09-24 13:07 UTC359
                encryption: encryption_queue,
31ce2453c82fatagen2026-09-24 13:07 UTC360
                session_changes,
f12377bce9b9atagen2026-09-26 12:32 UTC361
                persistence,
31ce2453c82fatagen2026-09-24 13:07 UTC362
            }),
5a52012c9b1datagen2026-09-24 23:45 UTC363
            verification_handler: None,
38c4caecdb3datagen2026-09-26 13:35 UTC364
            preparations: prepare::Preparations::default(),
fe4133dbcd47atagen2026-09-26 09:05 UTC365
            stop,
fe4133dbcd47atagen2026-09-26 09:05 UTC366
            stops,
673563a76e43atagen2026-09-26 12:30 UTC367
            logouts: Arc::new(watch::Sender::new(Logouts::default())),
a250c7fa43fbatagen2026-09-26 13:35 UTC368
            _store_lock: store_lock,
31ce2453c82fatagen2026-09-24 13:07 UTC369
        };
31ce2453c82fatagen2026-09-24 13:07 UTC370
        Ok((engine, handle, events))
31ce2453c82fatagen2026-09-24 13:07 UTC371
    }
31ce2453c82fatagen2026-09-24 13:07 UTC372
5a52012c9b1datagen2026-09-24 23:45 UTC373
    /// boxed because the engine's future is deep enough to blow the compiler's query depth
5a52012c9b1datagen2026-09-24 23:45 UTC374
    /// in every crate that spawns it, which isn't something a client should have to fix
fe4133dbcd47atagen2026-09-26 09:05 UTC375
    pub fn run(self) -> EngineBoxFuture<'static, Result<Termination, EngineError>> {
5a52012c9b1datagen2026-09-24 23:45 UTC376
        Box::pin(self.run_inner())
5a52012c9b1datagen2026-09-24 23:45 UTC377
    }
5a52012c9b1datagen2026-09-24 23:45 UTC378
fe4133dbcd47atagen2026-09-26 09:05 UTC379
    async fn run_inner(mut self) -> Result<Termination, EngineError> {
31ce2453c82fatagen2026-09-24 13:07 UTC380
        let events = self.port.event_sender();
31ce2453c82fatagen2026-09-24 13:07 UTC381
        let user_id = self
31ce2453c82fatagen2026-09-24 13:07 UTC382
            .client
31ce2453c82fatagen2026-09-24 13:07 UTC383
            .user_id()
31ce2453c82fatagen2026-09-24 13:07 UTC384
            .expect("authentication was checked during construction")
31ce2453c82fatagen2026-09-24 13:07 UTC385
            .to_owned();
31ce2453c82fatagen2026-09-24 13:07 UTC386
        self.port
31ce2453c82fatagen2026-09-24 13:07 UTC387
            .connection_sender()
31ce2453c82fatagen2026-09-24 13:07 UTC388
            .send_replace(ConnectionState::Connecting);
31ce2453c82fatagen2026-09-24 13:07 UTC389
        for event in [
31ce2453c82fatagen2026-09-24 13:07 UTC390
            Event::Authenticated { user_id },
31ce2453c82fatagen2026-09-24 13:07 UTC391
            Event::ConnectionChanged(ConnectionState::Connecting),
31ce2453c82fatagen2026-09-24 13:07 UTC392
        ] {
31ce2453c82fatagen2026-09-24 13:07 UTC393
            if events.send(event).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC394
                self.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC395
                return Err(EngineError::EventReceiverClosed);
31ce2453c82fatagen2026-09-24 13:07 UTC396
            }
31ce2453c82fatagen2026-09-24 13:07 UTC397
        }
31ce2453c82fatagen2026-09-24 13:07 UTC398
        self.start_tasks();
31ce2453c82fatagen2026-09-24 13:07 UTC399
        self.sync.start().await;
31ce2453c82fatagen2026-09-24 13:07 UTC400
31ce2453c82fatagen2026-09-24 13:07 UTC401
        let mut shutdown_reply = None;
673563a76e43atagen2026-09-26 12:30 UTC402
        let mut handles_dropped = false;
673563a76e43atagen2026-09-26 12:30 UTC403
        let mut logouts = self.logouts.subscribe();
fe4133dbcd47atagen2026-09-26 09:05 UTC404
        let ending = loop {
31ce2453c82fatagen2026-09-24 13:07 UTC405
            tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC406
                biased;
fe4133dbcd47atagen2026-09-26 09:05 UTC407
                Some(termination) = self.stops.recv() => break Ok(termination),
673563a76e43atagen2026-09-26 12:30 UTC408
                () = events.closed() => break Err(EngineError::EventReceiverClosed),
673563a76e43atagen2026-09-26 12:30 UTC409
                () = settled(&mut logouts), if handles_dropped => {
673563a76e43atagen2026-09-26 12:30 UTC410
                    break Ok(Termination::HandlesDropped);
673563a76e43atagen2026-09-26 12:30 UTC411
                }
1fe480c16a3catagen2026-09-26 08:44 UTC412
                Some((context, panic)) = self.tasks.join_next(), if !self.tasks.is_empty() => {
1fe480c16a3catagen2026-09-26 08:44 UTC413
                    let Some(message) = panic else { continue };
31ce2453c82fatagen2026-09-24 13:07 UTC414
                    if let TaskContext::Command(label) = context {
1fe480c16a3catagen2026-09-26 08:44 UTC415
                        tracing::error!(label, panic = %message, "command task panicked");
31ce2453c82fatagen2026-09-24 13:07 UTC416
                        continue;
31ce2453c82fatagen2026-09-24 13:07 UTC417
                    }
fe4133dbcd47atagen2026-09-26 09:05 UTC418
                    break Err(EngineError::TaskPanicked { task: context.label() });
31ce2453c82fatagen2026-09-24 13:07 UTC419
                }
5a52012c9b1datagen2026-09-24 23:45 UTC420
                Some(note) = self.notes.recv() => self.actor_note(note).await,
673563a76e43atagen2026-09-26 12:30 UTC421
                request = self.port.recv(), if !handles_dropped => {
673563a76e43atagen2026-09-26 12:30 UTC422
                    // nobody's left to ask, but a logout they asked for still deserves to land
673563a76e43atagen2026-09-26 12:30 UTC423
                    let Some(request) = request else {
673563a76e43atagen2026-09-26 12:30 UTC424
                        handles_dropped = true;
673563a76e43atagen2026-09-26 12:30 UTC425
                        continue;
673563a76e43atagen2026-09-26 12:30 UTC426
                    };
5a52012c9b1datagen2026-09-24 23:45 UTC427
                    if let Some(reply) = self.dispatch(request).await {
5a52012c9b1datagen2026-09-24 23:45 UTC428
                        shutdown_reply = Some(reply);
fe4133dbcd47atagen2026-09-26 09:05 UTC429
                        break Ok(Termination::ShutDown);
5a52012c9b1datagen2026-09-24 23:45 UTC430
                    }
5a52012c9b1datagen2026-09-24 23:45 UTC431
                }
31ce2453c82fatagen2026-09-24 13:07 UTC432
            }
fe4133dbcd47atagen2026-09-26 09:05 UTC433
        };
31ce2453c82fatagen2026-09-24 13:07 UTC434
fe4133dbcd47atagen2026-09-26 09:05 UTC435
        if let Err(error) = &ending {
31ce2453c82fatagen2026-09-24 13:07 UTC436
            let _ = events.send(Event::Failed(error.failure())).await;
31ce2453c82fatagen2026-09-24 13:07 UTC437
        }
31ce2453c82fatagen2026-09-24 13:07 UTC438
        self.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC439
        let _ = events.send(Event::Stopped).await;
31ce2453c82fatagen2026-09-24 13:07 UTC440
        if let Some(reply) = shutdown_reply {
31ce2453c82fatagen2026-09-24 13:07 UTC441
            reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC442
        }
5a52012c9b1datagen2026-09-24 23:45 UTC443
        // everything else still queued answers `Interrupted` as it drops, but a shutdown did happen
5a52012c9b1datagen2026-09-24 23:45 UTC444
        while let Some(Request { command, reply }) = self.port.try_recv() {
5a52012c9b1datagen2026-09-24 23:45 UTC445
            if matches!(command, Command::Lifecycle(LifecycleCommand::Shutdown)) {
5a52012c9b1datagen2026-09-24 23:45 UTC446
                reply.done(Ok(()));
5a52012c9b1datagen2026-09-24 23:45 UTC447
            }
5a52012c9b1datagen2026-09-24 23:45 UTC448
        }
fe4133dbcd47atagen2026-09-26 09:05 UTC449
        ending
31ce2453c82fatagen2026-09-24 13:07 UTC450
    }
31ce2453c82fatagen2026-09-24 13:07 UTC451
31ce2453c82fatagen2026-09-24 13:07 UTC452
    fn start_tasks(&mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC453
        let queues = self.queues.take().expect("engine tasks start once");
5a52012c9b1datagen2026-09-24 23:45 UTC454
        let (incoming, incoming_verifications) = mpsc::unbounded_channel();
5a52012c9b1datagen2026-09-24 23:45 UTC455
        self.verification_handler = Some(self.verification_handler(incoming));
31ce2453c82fatagen2026-09-24 13:07 UTC456
        let client = &self.client;
31ce2453c82fatagen2026-09-24 13:07 UTC457
        let events = || self.port.event_sender();
f12377bce9b9atagen2026-09-26 12:32 UTC458
        let (notices, notice_queue) = mpsc::unbounded_channel();
17e042fbc331atagen2026-10-08 12:39 UTC459
        let watchers: [(&'static str, EngineBoxFuture<'static, ()>); 13] = [
5a52012c9b1datagen2026-09-24 23:45 UTC460
            (
7c26fad34822atagen2026-09-26 09:41 UTC461
                "upload tracker",
0bfa3f46f0beatagen2026-09-26 09:12 UTC462
                Box::pin(watchers::uploads(
5a52012c9b1datagen2026-09-24 23:45 UTC463
                    client.clone(),
5a52012c9b1datagen2026-09-24 23:45 UTC464
                    client.send_queue().subscribe(),
5a52012c9b1datagen2026-09-24 23:45 UTC465
                    events(),
5a52012c9b1datagen2026-09-24 23:45 UTC466
                )),
5a52012c9b1datagen2026-09-24 23:45 UTC467
            ),
5a52012c9b1datagen2026-09-24 23:45 UTC468
            (
5a52012c9b1datagen2026-09-24 23:45 UTC469
                "room subscription watcher",
5a52012c9b1datagen2026-09-24 23:45 UTC470
                Box::pin(watchers::room_subscriptions(
5a52012c9b1datagen2026-09-24 23:45 UTC471
                    self.sync.clone(),
5a52012c9b1datagen2026-09-24 23:45 UTC472
                    self.subscriptions.subscribe(),
5a52012c9b1datagen2026-09-24 23:45 UTC473
                )),
5a52012c9b1datagen2026-09-24 23:45 UTC474
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC475
            (
31ce2453c82fatagen2026-09-24 13:07 UTC476
                "session change watcher",
f12377bce9b9atagen2026-09-26 12:32 UTC477
                Box::pin(session::session(
f12377bce9b9atagen2026-09-26 12:32 UTC478
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC479
                    queues.session_changes,
f12377bce9b9atagen2026-09-26 12:32 UTC480
                    queues.persistence,
f12377bce9b9atagen2026-09-26 12:32 UTC481
                    notices,
fe4133dbcd47atagen2026-09-26 09:05 UTC482
                    client
fe4133dbcd47atagen2026-09-26 09:05 UTC483
                        .device_id()
fe4133dbcd47atagen2026-09-26 09:05 UTC484
                        .expect("authentication was checked during construction")
fe4133dbcd47atagen2026-09-26 09:05 UTC485
                        .to_owned(),
673563a76e43atagen2026-09-26 12:30 UTC486
                    self.logouts.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC487
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC488
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC489
            (
f12377bce9b9atagen2026-09-26 12:32 UTC490
                "session notice watcher",
f12377bce9b9atagen2026-09-26 12:32 UTC491
                Box::pin(session::session_notices(
f12377bce9b9atagen2026-09-26 12:32 UTC492
                    notice_queue,
f12377bce9b9atagen2026-09-26 12:32 UTC493
                    events(),
f12377bce9b9atagen2026-09-26 12:32 UTC494
                    self.stop.clone(),
f12377bce9b9atagen2026-09-26 12:32 UTC495
                )),
f12377bce9b9atagen2026-09-26 12:32 UTC496
            ),
f12377bce9b9atagen2026-09-26 12:32 UTC497
            (
31ce2453c82fatagen2026-09-24 13:07 UTC498
                "connection state watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC499
                Box::pin(watchers::connection(
31ce2453c82fatagen2026-09-24 13:07 UTC500
                    self.sync.state(),
31ce2453c82fatagen2026-09-24 13:07 UTC501
                    self.sync.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC502
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC503
                    events(),
31ce2453c82fatagen2026-09-24 13:07 UTC504
                    self.port.connection_sender(),
31ce2453c82fatagen2026-09-24 13:07 UTC505
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC506
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC507
            (
31ce2453c82fatagen2026-09-24 13:07 UTC508
                "send queue watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC509
                Box::pin(watchers::send_queue_errors(
31ce2453c82fatagen2026-09-24 13:07 UTC510
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC511
                    client.send_queue().subscribe_errors(),
31ce2453c82fatagen2026-09-24 13:07 UTC512
                    self.port.connection_sender().subscribe(),
31ce2453c82fatagen2026-09-24 13:07 UTC513
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC514
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC515
            (
31ce2453c82fatagen2026-09-24 13:07 UTC516
                "encryption state watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC517
                Box::pin(watchers::encryption(client.clone(), events())),
31ce2453c82fatagen2026-09-24 13:07 UTC518
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC519
            (
31ce2453c82fatagen2026-09-24 13:07 UTC520
                "room list watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC521
                Box::pin(watchers::rooms(client.clone(), events())),
31ce2453c82fatagen2026-09-24 13:07 UTC522
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC523
            (
17e042fbc331atagen2026-10-08 12:39 UTC524
                "image pack watcher",
17e042fbc331atagen2026-10-08 12:39 UTC525
                Box::pin(watchers::image_packs(client.clone(), events())),
17e042fbc331atagen2026-10-08 12:39 UTC526
            ),
17e042fbc331atagen2026-10-08 12:39 UTC527
            (
31ce2453c82fatagen2026-09-24 13:07 UTC528
                "notification settings watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC529
                Box::pin(watchers::notification_settings(client.clone(), events())),
31ce2453c82fatagen2026-09-24 13:07 UTC530
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC531
            (
31ce2453c82fatagen2026-09-24 13:07 UTC532
                "room command worker",
31ce2453c82fatagen2026-09-24 13:07 UTC533
                Box::pin(mutations::run_scheduler(
31ce2453c82fatagen2026-09-24 13:07 UTC534
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC535
                    queues.mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC536
                    self.config.command_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC537
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC538
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC539
            (
31ce2453c82fatagen2026-09-24 13:07 UTC540
                "notification command worker",
31ce2453c82fatagen2026-09-24 13:07 UTC541
                Box::pin(commands::run_notifications(
31ce2453c82fatagen2026-09-24 13:07 UTC542
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC543
                    queues.notifications,
31ce2453c82fatagen2026-09-24 13:07 UTC544
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC545
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC546
            (
31ce2453c82fatagen2026-09-24 13:07 UTC547
                "encryption command worker",
31ce2453c82fatagen2026-09-24 13:07 UTC548
                Box::pin(commands::run_encryption(
31ce2453c82fatagen2026-09-24 13:07 UTC549
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC550
                    events(),
31ce2453c82fatagen2026-09-24 13:07 UTC551
                    queues.encryption,
5a52012c9b1datagen2026-09-24 23:45 UTC552
                    incoming_verifications,
31ce2453c82fatagen2026-09-24 13:07 UTC553
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC554
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC555
        ];
31ce2453c82fatagen2026-09-24 13:07 UTC556
        for (label, task) in watchers {
31ce2453c82fatagen2026-09-24 13:07 UTC557
            self.tasks.spawn(TaskContext::Watcher(label), task);
31ce2453c82fatagen2026-09-24 13:07 UTC558
        }
31ce2453c82fatagen2026-09-24 13:07 UTC559
    }
31ce2453c82fatagen2026-09-24 13:07 UTC560
5a52012c9b1datagen2026-09-24 23:45 UTC561
    fn verification_handler(
5a52012c9b1datagen2026-09-24 23:45 UTC562
        &self,
5a52012c9b1datagen2026-09-24 23:45 UTC563
        incoming: IncomingVerifications,
5a52012c9b1datagen2026-09-24 23:45 UTC564
    ) -> matrix_sdk::event_handler::EventHandlerHandle {
5a52012c9b1datagen2026-09-24 23:45 UTC565
        use matrix_sdk::ruma::events::key::verification::request::ToDeviceKeyVerificationRequestEvent;
5a52012c9b1datagen2026-09-24 23:45 UTC566
        self.client.add_event_handler(
5a52012c9b1datagen2026-09-24 23:45 UTC567
            move |event: ToDeviceKeyVerificationRequestEvent, client: Client| {
5a52012c9b1datagen2026-09-24 23:45 UTC568
                let incoming = incoming.clone();
5a52012c9b1datagen2026-09-24 23:45 UTC569
                async move {
5a52012c9b1datagen2026-09-24 23:45 UTC570
                    let request = client
5a52012c9b1datagen2026-09-24 23:45 UTC571
                        .encryption()
5a52012c9b1datagen2026-09-24 23:45 UTC572
                        .get_verification_request(
5a52012c9b1datagen2026-09-24 23:45 UTC573
                            &event.sender,
5a52012c9b1datagen2026-09-24 23:45 UTC574
                            event.content.transaction_id.as_str(),
5a52012c9b1datagen2026-09-24 23:45 UTC575
                        )
5a52012c9b1datagen2026-09-24 23:45 UTC576
                        .await;
5a52012c9b1datagen2026-09-24 23:45 UTC577
                    if let Some(request) = request {
5a52012c9b1datagen2026-09-24 23:45 UTC578
                        let _ = incoming.send(request);
5a52012c9b1datagen2026-09-24 23:45 UTC579
                    }
5a52012c9b1datagen2026-09-24 23:45 UTC580
                }
5a52012c9b1datagen2026-09-24 23:45 UTC581
            },
5a52012c9b1datagen2026-09-24 23:45 UTC582
        )
5a52012c9b1datagen2026-09-24 23:45 UTC583
    }
5a52012c9b1datagen2026-09-24 23:45 UTC584
31ce2453c82fatagen2026-09-24 13:07 UTC585
    /// hands back the shutdown request's responder, which is answered once we've stopped
31ce2453c82fatagen2026-09-24 13:07 UTC586
    async fn dispatch(&mut self, Request { command, reply }: Request) -> Option<Responder> {
31ce2453c82fatagen2026-09-24 13:07 UTC587
        let client = self.client.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC588
        match command {
31ce2453c82fatagen2026-09-24 13:07 UTC589
            Command::Lifecycle(LifecycleCommand::Shutdown) => return Some(reply),
31ce2453c82fatagen2026-09-24 13:07 UTC590
            Command::Account(AccountCommand::Logout) => {
31ce2453c82fatagen2026-09-24 13:07 UTC591
                let events = self.port.event_sender();
31ce2453c82fatagen2026-09-24 13:07 UTC592
                let stop = self.stop.clone();
673563a76e43atagen2026-09-26 12:30 UTC593
                let logouts = self.logouts.clone();
673563a76e43atagen2026-09-26 12:30 UTC594
                logouts.send_modify(|logouts| logouts.in_flight += 1);
31ce2453c82fatagen2026-09-24 13:07 UTC595
                self.spawn_command("logout", reply, async move {
673563a76e43atagen2026-09-26 12:30 UTC596
                    let result = client.logout().await;
673563a76e43atagen2026-09-26 12:30 UTC597
                    if result.is_ok() {
673563a76e43atagen2026-09-26 12:30 UTC598
                        let _ = events.send(Event::LoggedOut).await;
673563a76e43atagen2026-09-26 12:30 UTC599
                        let _ = stop.send(Termination::LoggedOut);
673563a76e43atagen2026-09-26 12:30 UTC600
                    }
673563a76e43atagen2026-09-26 12:30 UTC601
                    let mut rejected = None;
673563a76e43atagen2026-09-26 12:30 UTC602
                    logouts.send_if_modified(|logouts| {
673563a76e43atagen2026-09-26 12:30 UTC603
                        if result.is_err() && logouts.in_flight == 1 {
673563a76e43atagen2026-09-26 12:30 UTC604
                            rejected = logouts.rejected.take();
673563a76e43atagen2026-09-26 12:30 UTC605
                        }
673563a76e43atagen2026-09-26 12:30 UTC606
                        false
673563a76e43atagen2026-09-26 12:30 UTC607
                    });
673563a76e43atagen2026-09-26 12:30 UTC608
                    if let Some(soft_logout) = rejected {
f12377bce9b9atagen2026-09-26 12:32 UTC609
                        session::reject_session(&events, &stop, soft_logout).await;
673563a76e43atagen2026-09-26 12:30 UTC610
                    }
673563a76e43atagen2026-09-26 12:30 UTC611
                    logouts.send_modify(|logouts| logouts.in_flight -= 1);
673563a76e43atagen2026-09-26 12:30 UTC612
                    result.map_err(|source| Error::from_sdk("logout failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC613
                });
31ce2453c82fatagen2026-09-24 13:07 UTC614
            }
31ce2453c82fatagen2026-09-24 13:07 UTC615
            Command::Account(AccountCommand::SetAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC616
                event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC617
                content,
31ce2453c82fatagen2026-09-24 13:07 UTC618
            }) => enqueue(
31ce2453c82fatagen2026-09-24 13:07 UTC619
                &self.mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC620
                Job {
31ce2453c82fatagen2026-09-24 13:07 UTC621
                    command: Mutation::GlobalAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC622
                        event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC623
                        content,
31ce2453c82fatagen2026-09-24 13:07 UTC624
                    },
31ce2453c82fatagen2026-09-24 13:07 UTC625
                    reply,
31ce2453c82fatagen2026-09-24 13:07 UTC626
                },
31ce2453c82fatagen2026-09-24 13:07 UTC627
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC628
            Command::Account(AccountCommand::SetDisplayName(name)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC629
                self.spawn_command("set display name", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC630
                    client
31ce2453c82fatagen2026-09-24 13:07 UTC631
                        .account()
31ce2453c82fatagen2026-09-24 13:07 UTC632
                        .set_display_name(name.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC633
                        .await
31ce2453c82fatagen2026-09-24 13:07 UTC634
                        .map_err(|source| Error::from_sdk("setting display name failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC635
                });
31ce2453c82fatagen2026-09-24 13:07 UTC636
            }
31ce2453c82fatagen2026-09-24 13:07 UTC637
            Command::Account(AccountCommand::SetAvatar(avatar)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC638
                self.spawn_command("set avatar", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC639
                    client
31ce2453c82fatagen2026-09-24 13:07 UTC640
                        .account()
31ce2453c82fatagen2026-09-24 13:07 UTC641
                        .set_avatar_url(avatar.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC642
                        .await
31ce2453c82fatagen2026-09-24 13:07 UTC643
                        .map_err(|source| Error::from_sdk("setting avatar failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC644
                });
31ce2453c82fatagen2026-09-24 13:07 UTC645
            }
31ce2453c82fatagen2026-09-24 13:07 UTC646
            Command::Account(AccountCommand::UploadAvatar { mimetype, bytes }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC647
                let max_bytes = self.config.max_media_bytes;
31ce2453c82fatagen2026-09-24 13:07 UTC648
                self.spawn_command("upload avatar", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC649
                    commands::upload_avatar(&client, &mimetype, bytes, max_bytes).await
31ce2453c82fatagen2026-09-24 13:07 UTC650
                });
31ce2453c82fatagen2026-09-24 13:07 UTC651
            }
31ce2453c82fatagen2026-09-24 13:07 UTC652
            Command::Account(AccountCommand::RenameDevice {
31ce2453c82fatagen2026-09-24 13:07 UTC653
                device_id,
31ce2453c82fatagen2026-09-24 13:07 UTC654
                display_name,
31ce2453c82fatagen2026-09-24 13:07 UTC655
            }) => self.spawn_command("rename device", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC656
                commands::rename_device(&client, &device_id, &display_name).await
31ce2453c82fatagen2026-09-24 13:07 UTC657
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC658
            Command::Account(AccountCommand::DeleteDevices { device_ids, auth }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC659
                self.spawn_command("delete devices", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC660
                    commands::delete_devices(&client, &device_ids, auth).await
31ce2453c82fatagen2026-09-24 13:07 UTC661
                });
31ce2453c82fatagen2026-09-24 13:07 UTC662
            }
38c4caecdb3datagen2026-09-26 13:35 UTC663
            Command::Room(RoomCommand::Open(room_id)) => {
38c4caecdb3datagen2026-09-26 13:35 UTC664
                if self.config.prepare_to_send {
38c4caecdb3datagen2026-09-26 13:35 UTC665
                    self.prepare_quietly(room_id.clone());
38c4caecdb3datagen2026-09-26 13:35 UTC666
                }
38c4caecdb3datagen2026-09-26 13:35 UTC667
                self.open_room(room_id, reply).await;
38c4caecdb3datagen2026-09-26 13:35 UTC668
            }
31ce2453c82fatagen2026-09-24 13:07 UTC669
            Command::Room(RoomCommand::Close(room_id)) => self.close_room(room_id, reply).await,
38c4caecdb3datagen2026-09-26 13:35 UTC670
            Command::Room(RoomCommand::PrepareToSend(room_id)) => {
38c4caecdb3datagen2026-09-26 13:35 UTC671
                let flight = self.preparations.join(&client, room_id);
38c4caecdb3datagen2026-09-26 13:35 UTC672
                self.spawn_command("prepare to send", reply, flight);
38c4caecdb3datagen2026-09-26 13:35 UTC673
            }
31ce2453c82fatagen2026-09-24 13:07 UTC674
            Command::Room(RoomCommand::SetTyping { room_id, typing }) => {
38c4caecdb3datagen2026-09-26 13:35 UTC675
                if typing && self.config.prepare_to_send {
38c4caecdb3datagen2026-09-26 13:35 UTC676
                    self.prepare_quietly(room_id.clone());
38c4caecdb3datagen2026-09-26 13:35 UTC677
                }
31ce2453c82fatagen2026-09-24 13:07 UTC678
                self.spawn_command("typing", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC679
                    commands::set_typing(&client, &room_id, typing).await
31ce2453c82fatagen2026-09-24 13:07 UTC680
                });
31ce2453c82fatagen2026-09-24 13:07 UTC681
            }
31ce2453c82fatagen2026-09-24 13:07 UTC682
            Command::Room(RoomCommand::CancelSend {
31ce2453c82fatagen2026-09-24 13:07 UTC683
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC684
                transaction_id,
31ce2453c82fatagen2026-09-24 13:07 UTC685
            }) => self.spawn_command("cancel send", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC686
                commands::cancel_send(&client, &room_id, &transaction_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC687
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC688
            Command::Room(RoomCommand::RetrySend {
31ce2453c82fatagen2026-09-24 13:07 UTC689
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC690
                transaction_id,
31ce2453c82fatagen2026-09-24 13:07 UTC691
            }) => self.spawn_command("retry send", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC692
                commands::retry_send(&client, &room_id, &transaction_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC693
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC694
            Command::Room(command) => enqueue(
31ce2453c82fatagen2026-09-24 13:07 UTC695
                &self.mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC696
                Job {
31ce2453c82fatagen2026-09-24 13:07 UTC697
                    command: Mutation::Room(command),
31ce2453c82fatagen2026-09-24 13:07 UTC698
                    reply,
31ce2453c82fatagen2026-09-24 13:07 UTC699
                },
31ce2453c82fatagen2026-09-24 13:07 UTC700
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC701
            Command::Timeline(command) => self.route_timeline(command, reply),
31ce2453c82fatagen2026-09-24 13:07 UTC702
            Command::Media(MediaCommand::Upload(upload)) => self.upload(upload, reply),
31ce2453c82fatagen2026-09-24 13:07 UTC703
            Command::Encryption(command) => enqueue(&self.encryption, Job { command, reply }),
31ce2453c82fatagen2026-09-24 13:07 UTC704
            Command::Notification(command) => {
31ce2453c82fatagen2026-09-24 13:07 UTC705
                enqueue(&self.notifications, Job { command, reply });
31ce2453c82fatagen2026-09-24 13:07 UTC706
            }
31ce2453c82fatagen2026-09-24 13:07 UTC707
        }
31ce2453c82fatagen2026-09-24 13:07 UTC708
        None
31ce2453c82fatagen2026-09-24 13:07 UTC709
    }
31ce2453c82fatagen2026-09-24 13:07 UTC710
31ce2453c82fatagen2026-09-24 13:07 UTC711
    fn spawn_command(
31ce2453c82fatagen2026-09-24 13:07 UTC712
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC713
        label: &'static str,
31ce2453c82fatagen2026-09-24 13:07 UTC714
        reply: Responder,
1fe480c16a3catagen2026-09-26 08:44 UTC715
        work: impl Future<Output = Result<(), Error>> + SendOutsideWasm + 'static,
31ce2453c82fatagen2026-09-24 13:07 UTC716
    ) {
31ce2453c82fatagen2026-09-24 13:07 UTC717
        self.tasks.spawn(TaskContext::Command(label), async move {
31ce2453c82fatagen2026-09-24 13:07 UTC718
            reply.done(work.await);
31ce2453c82fatagen2026-09-24 13:07 UTC719
        });
31ce2453c82fatagen2026-09-24 13:07 UTC720
    }
31ce2453c82fatagen2026-09-24 13:07 UTC721
38c4caecdb3datagen2026-09-26 13:35 UTC722
    fn prepare_quietly(&mut self, room_id: OwnedRoomId) {
38c4caecdb3datagen2026-09-26 13:35 UTC723
        let flight = self.preparations.join(&self.client, room_id);
38c4caecdb3datagen2026-09-26 13:35 UTC724
        self.tasks
38c4caecdb3datagen2026-09-26 13:35 UTC725
            .spawn(TaskContext::Command("prepare to send"), async move {
38c4caecdb3datagen2026-09-26 13:35 UTC726
                let _ = flight.await;
38c4caecdb3datagen2026-09-26 13:35 UTC727
            });
38c4caecdb3datagen2026-09-26 13:35 UTC728
    }
38c4caecdb3datagen2026-09-26 13:35 UTC729
31ce2453c82fatagen2026-09-24 13:07 UTC730
    fn upload(&mut self, upload: crate::AttachmentUpload, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC731
        let Some(room) = self.client.get_room(upload.timeline_id.room_id()) else {
31ce2453c82fatagen2026-09-24 13:07 UTC732
            return reply.done(Err(Error::room_unknown()));
31ce2453c82fatagen2026-09-24 13:07 UTC733
        };
31ce2453c82fatagen2026-09-24 13:07 UTC734
        let max_bytes = self.config.max_media_bytes;
31ce2453c82fatagen2026-09-24 13:07 UTC735
        self.spawn_command("upload", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC736
            media::queue_upload(room, upload, max_bytes).await
31ce2453c82fatagen2026-09-24 13:07 UTC737
        });
31ce2453c82fatagen2026-09-24 13:07 UTC738
    }
31ce2453c82fatagen2026-09-24 13:07 UTC739
31ce2453c82fatagen2026-09-24 13:07 UTC740
    fn route_timeline(&mut self, command: TimelineCommand, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC741
        let sender = match self.rooms.get(command.room_id()) {
31ce2453c82fatagen2026-09-24 13:07 UTC742
            Some(RoomSlot::Active { sender, .. }) => Some(sender.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC743
            _ => None,
31ce2453c82fatagen2026-09-24 13:07 UTC744
        };
31ce2453c82fatagen2026-09-24 13:07 UTC745
        match (sender, command) {
31ce2453c82fatagen2026-09-24 13:07 UTC746
            (Some(sender), command) => send_actor(&sender, ActorRequest::Execute(command, reply)),
31ce2453c82fatagen2026-09-24 13:07 UTC747
            (
31ce2453c82fatagen2026-09-24 13:07 UTC748
                None,
31ce2453c82fatagen2026-09-24 13:07 UTC749
                TimelineCommand::MarkRead {
31ce2453c82fatagen2026-09-24 13:07 UTC750
                    timeline_id,
31ce2453c82fatagen2026-09-24 13:07 UTC751
                    event_id,
31ce2453c82fatagen2026-09-24 13:07 UTC752
                },
31ce2453c82fatagen2026-09-24 13:07 UTC753
            ) => {
31ce2453c82fatagen2026-09-24 13:07 UTC754
                let client = self.client.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC755
                self.spawn_command("mark read", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC756
                    commands::mark_read(&client, &timeline_id, event_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC757
                });
31ce2453c82fatagen2026-09-24 13:07 UTC758
            }
31ce2453c82fatagen2026-09-24 13:07 UTC759
            (None, _) => reply.done(Err(Error::new(ErrorKind::RoomNotOpen, "room is not open"))),
31ce2453c82fatagen2026-09-24 13:07 UTC760
        }
31ce2453c82fatagen2026-09-24 13:07 UTC761
    }
31ce2453c82fatagen2026-09-24 13:07 UTC762
31ce2453c82fatagen2026-09-24 13:07 UTC763
    async fn open_room(&mut self, room_id: OwnedRoomId, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC764
        match self.rooms.get_mut(&room_id) {
31ce2453c82fatagen2026-09-24 13:07 UTC765
            Some(RoomSlot::Active { sender, .. }) => send_actor(sender, ActorRequest::Open(reply)),
31ce2453c82fatagen2026-09-24 13:07 UTC766
            Some(RoomSlot::Closing { pending, .. }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC767
                defer(pending, Deferred::Open(reply), self.config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC768
            }
31ce2453c82fatagen2026-09-24 13:07 UTC769
            None => self.start_actor(room_id, reply).await,
31ce2453c82fatagen2026-09-24 13:07 UTC770
        }
31ce2453c82fatagen2026-09-24 13:07 UTC771
    }
31ce2453c82fatagen2026-09-24 13:07 UTC772
31ce2453c82fatagen2026-09-24 13:07 UTC773
    async fn close_room(&mut self, room_id: OwnedRoomId, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC774
        let (generation, sender) = match self.rooms.get_mut(&room_id) {
31ce2453c82fatagen2026-09-24 13:07 UTC775
            None => return reply.done(Ok(())),
31ce2453c82fatagen2026-09-24 13:07 UTC776
            Some(RoomSlot::Closing { pending, .. }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC777
                return defer(
31ce2453c82fatagen2026-09-24 13:07 UTC778
                    pending,
31ce2453c82fatagen2026-09-24 13:07 UTC779
                    Deferred::Close(reply),
31ce2453c82fatagen2026-09-24 13:07 UTC780
                    self.config.command_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC781
                );
31ce2453c82fatagen2026-09-24 13:07 UTC782
            }
31ce2453c82fatagen2026-09-24 13:07 UTC783
            Some(RoomSlot::Active { generation, sender }) => (*generation, sender.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC784
        };
31ce2453c82fatagen2026-09-24 13:07 UTC785
        match sender.try_send(ActorRequest::Close(reply)) {
5a52012c9b1datagen2026-09-24 23:45 UTC786
            Ok(()) => {}
5a52012c9b1datagen2026-09-24 23:45 UTC787
            // the actor already left on its own, which is closed enough
5a52012c9b1datagen2026-09-24 23:45 UTC788
            Err(mpsc::error::TrySendError::Closed(request)) => request.into_reply().done(Ok(())),
31ce2453c82fatagen2026-09-24 13:07 UTC789
            Err(mpsc::error::TrySendError::Full(request)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC790
                return request.into_reply().done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC791
                    ErrorKind::RoomBusy,
31ce2453c82fatagen2026-09-24 13:07 UTC792
                    "room command queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC793
                )));
31ce2453c82fatagen2026-09-24 13:07 UTC794
            }
31ce2453c82fatagen2026-09-24 13:07 UTC795
        }
31ce2453c82fatagen2026-09-24 13:07 UTC796
        self.rooms.insert(
31ce2453c82fatagen2026-09-24 13:07 UTC797
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC798
            RoomSlot::Closing {
31ce2453c82fatagen2026-09-24 13:07 UTC799
                generation,
31ce2453c82fatagen2026-09-24 13:07 UTC800
                pending: VecDeque::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC801
            },
31ce2453c82fatagen2026-09-24 13:07 UTC802
        );
5a52012c9b1datagen2026-09-24 23:45 UTC803
        self.update_room_subscriptions();
31ce2453c82fatagen2026-09-24 13:07 UTC804
    }
31ce2453c82fatagen2026-09-24 13:07 UTC805
31ce2453c82fatagen2026-09-24 13:07 UTC806
    async fn start_actor(&mut self, room_id: OwnedRoomId, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC807
        let Some(room) = self.client.get_room(&room_id) else {
31ce2453c82fatagen2026-09-24 13:07 UTC808
            return reply.done(Err(Error::room_unknown()));
31ce2453c82fatagen2026-09-24 13:07 UTC809
        };
31ce2453c82fatagen2026-09-24 13:07 UTC810
        self.next_generation = self.next_generation.wrapping_add(1);
31ce2453c82fatagen2026-09-24 13:07 UTC811
        let generation = self.next_generation;
31ce2453c82fatagen2026-09-24 13:07 UTC812
        let (sender, requests) = mpsc::channel(self.config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC813
        let (supervisor, panics) = Supervisor::new();
31ce2453c82fatagen2026-09-24 13:07 UTC814
        let actor = RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC815
            room_id: room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC816
            generation,
31ce2453c82fatagen2026-09-24 13:07 UTC817
            room,
31ce2453c82fatagen2026-09-24 13:07 UTC818
            requests,
5a52012c9b1datagen2026-09-24 23:45 UTC819
            notes: self.note_sender.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC820
            context: SessionContext {
31ce2453c82fatagen2026-09-24 13:07 UTC821
                events: self.port.event_sender(),
31ce2453c82fatagen2026-09-24 13:07 UTC822
                media: self.media.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC823
                current_user_id: self.client.user_id().map(ToOwned::to_owned),
31ce2453c82fatagen2026-09-24 13:07 UTC824
                initial_history_limit: self.config.initial_history_limit,
31ce2453c82fatagen2026-09-24 13:07 UTC825
                supervisor,
31ce2453c82fatagen2026-09-24 13:07 UTC826
            },
31ce2453c82fatagen2026-09-24 13:07 UTC827
            panics,
31ce2453c82fatagen2026-09-24 13:07 UTC828
            opening: vec![reply],
31ce2453c82fatagen2026-09-24 13:07 UTC829
        };
31ce2453c82fatagen2026-09-24 13:07 UTC830
        self.rooms
31ce2453c82fatagen2026-09-24 13:07 UTC831
            .insert(room_id.clone(), RoomSlot::Active { generation, sender });
31ce2453c82fatagen2026-09-24 13:07 UTC832
        self.tasks.spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC833
            TaskContext::RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC834
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC835
                generation,
31ce2453c82fatagen2026-09-24 13:07 UTC836
            },
31ce2453c82fatagen2026-09-24 13:07 UTC837
            actor.run(),
31ce2453c82fatagen2026-09-24 13:07 UTC838
        );
5a52012c9b1datagen2026-09-24 23:45 UTC839
        self.update_room_subscriptions();
31ce2453c82fatagen2026-09-24 13:07 UTC840
    }
31ce2453c82fatagen2026-09-24 13:07 UTC841
5a52012c9b1datagen2026-09-24 23:45 UTC842
    async fn actor_note(&mut self, note: ActorNote) {
5a52012c9b1datagen2026-09-24 23:45 UTC843
        let (room_id, generation) = match &note {
5a52012c9b1datagen2026-09-24 23:45 UTC844
            ActorNote::Leaving {
5a52012c9b1datagen2026-09-24 23:45 UTC845
                room_id,
5a52012c9b1datagen2026-09-24 23:45 UTC846
                generation,
5a52012c9b1datagen2026-09-24 23:45 UTC847
            }
5a52012c9b1datagen2026-09-24 23:45 UTC848
            | ActorNote::Exited {
5a52012c9b1datagen2026-09-24 23:45 UTC849
                room_id,
5a52012c9b1datagen2026-09-24 23:45 UTC850
                generation,
5a52012c9b1datagen2026-09-24 23:45 UTC851
                ..
5a52012c9b1datagen2026-09-24 23:45 UTC852
            } => (room_id.clone(), *generation),
5a52012c9b1datagen2026-09-24 23:45 UTC853
        };
31ce2453c82fatagen2026-09-24 13:07 UTC854
        if self
31ce2453c82fatagen2026-09-24 13:07 UTC855
            .rooms
31ce2453c82fatagen2026-09-24 13:07 UTC856
            .get(&room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC857
            .is_none_or(|slot| slot.generation() != generation)
31ce2453c82fatagen2026-09-24 13:07 UTC858
        {
31ce2453c82fatagen2026-09-24 13:07 UTC859
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC860
        }
5a52012c9b1datagen2026-09-24 23:45 UTC861
        let ActorNote::Exited { leftovers, .. } = note else {
5a52012c9b1datagen2026-09-24 23:45 UTC862
            if let Some(slot @ RoomSlot::Active { .. }) = self.rooms.get_mut(&room_id) {
5a52012c9b1datagen2026-09-24 23:45 UTC863
                *slot = RoomSlot::Closing {
5a52012c9b1datagen2026-09-24 23:45 UTC864
                    generation,
5a52012c9b1datagen2026-09-24 23:45 UTC865
                    pending: VecDeque::new(),
5a52012c9b1datagen2026-09-24 23:45 UTC866
                };
5a52012c9b1datagen2026-09-24 23:45 UTC867
                self.update_room_subscriptions();
5a52012c9b1datagen2026-09-24 23:45 UTC868
            }
5a52012c9b1datagen2026-09-24 23:45 UTC869
            return;
5a52012c9b1datagen2026-09-24 23:45 UTC870
        };
5a52012c9b1datagen2026-09-24 23:45 UTC871
        let pending = match self.rooms.remove(&room_id) {
5a52012c9b1datagen2026-09-24 23:45 UTC872
            Some(RoomSlot::Closing { pending, .. }) => pending,
5a52012c9b1datagen2026-09-24 23:45 UTC873
            _ => VecDeque::new(),
5a52012c9b1datagen2026-09-24 23:45 UTC874
        };
5a52012c9b1datagen2026-09-24 23:45 UTC875
        // leftovers were sent before anything deferred, so they go first
5a52012c9b1datagen2026-09-24 23:45 UTC876
        for request in leftovers {
5a52012c9b1datagen2026-09-24 23:45 UTC877
            match request {
5a52012c9b1datagen2026-09-24 23:45 UTC878
                ActorRequest::Open(reply) => self.open_room(room_id.clone(), reply).await,
5a52012c9b1datagen2026-09-24 23:45 UTC879
                ActorRequest::Close(reply) => reply.done(Ok(())),
5a52012c9b1datagen2026-09-24 23:45 UTC880
                ActorRequest::Execute(command, reply) => self.route_timeline(command, reply),
31ce2453c82fatagen2026-09-24 13:07 UTC881
            }
31ce2453c82fatagen2026-09-24 13:07 UTC882
        }
5a52012c9b1datagen2026-09-24 23:45 UTC883
        for deferred in pending {
5a52012c9b1datagen2026-09-24 23:45 UTC884
            match deferred {
5a52012c9b1datagen2026-09-24 23:45 UTC885
                Deferred::Open(reply) => self.open_room(room_id.clone(), reply).await,
5a52012c9b1datagen2026-09-24 23:45 UTC886
                Deferred::Close(reply) => self.close_room(room_id.clone(), reply).await,
5a52012c9b1datagen2026-09-24 23:45 UTC887
            }
5a52012c9b1datagen2026-09-24 23:45 UTC888
        }
5a52012c9b1datagen2026-09-24 23:45 UTC889
        self.update_room_subscriptions();
31ce2453c82fatagen2026-09-24 13:07 UTC890
    }
31ce2453c82fatagen2026-09-24 13:07 UTC891
5a52012c9b1datagen2026-09-24 23:45 UTC892
    fn update_room_subscriptions(&self) {
31ce2453c82fatagen2026-09-24 13:07 UTC893
        let room_ids = self
31ce2453c82fatagen2026-09-24 13:07 UTC894
            .rooms
31ce2453c82fatagen2026-09-24 13:07 UTC895
            .iter()
31ce2453c82fatagen2026-09-24 13:07 UTC896
            .filter(|(_, slot)| matches!(slot, RoomSlot::Active { .. }))
5a52012c9b1datagen2026-09-24 23:45 UTC897
            .map(|(room_id, _)| room_id.clone())
5a52012c9b1datagen2026-09-24 23:45 UTC898
            .collect();
5a52012c9b1datagen2026-09-24 23:45 UTC899
        self.subscriptions.send_replace(room_ids);
31ce2453c82fatagen2026-09-24 13:07 UTC900
    }
31ce2453c82fatagen2026-09-24 13:07 UTC901
31ce2453c82fatagen2026-09-24 13:07 UTC902
    async fn shutdown(&mut self) {
5a52012c9b1datagen2026-09-24 23:45 UTC903
        if let Some(handler) = self.verification_handler.take() {
5a52012c9b1datagen2026-09-24 23:45 UTC904
            self.client.remove_event_handler(handler);
5a52012c9b1datagen2026-09-24 23:45 UTC905
        }
31ce2453c82fatagen2026-09-24 13:07 UTC906
        self.port.close();
31ce2453c82fatagen2026-09-24 13:07 UTC907
        self.tasks.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC908
        self.rooms.clear();
31ce2453c82fatagen2026-09-24 13:07 UTC909
        self.media.clear().await;
31ce2453c82fatagen2026-09-24 13:07 UTC910
        self.sync.stop().await;
31ce2453c82fatagen2026-09-24 13:07 UTC911
        self.port
31ce2453c82fatagen2026-09-24 13:07 UTC912
            .connection_sender()
31ce2453c82fatagen2026-09-24 13:07 UTC913
            .send_replace(ConnectionState::Stopped);
31ce2453c82fatagen2026-09-24 13:07 UTC914
        let _ = self
31ce2453c82fatagen2026-09-24 13:07 UTC915
            .port
31ce2453c82fatagen2026-09-24 13:07 UTC916
            .event_sender()
31ce2453c82fatagen2026-09-24 13:07 UTC917
            .send(Event::ConnectionChanged(ConnectionState::Stopped))
31ce2453c82fatagen2026-09-24 13:07 UTC918
            .await;
31ce2453c82fatagen2026-09-24 13:07 UTC919
    }
31ce2453c82fatagen2026-09-24 13:07 UTC920
}
31ce2453c82fatagen2026-09-24 13:07 UTC921
31ce2453c82fatagen2026-09-24 13:07 UTC922
fn send_actor(sender: &mpsc::Sender<ActorRequest>, request: ActorRequest) {
31ce2453c82fatagen2026-09-24 13:07 UTC923
    if let Err(error) = sender.try_send(request) {
31ce2453c82fatagen2026-09-24 13:07 UTC924
        let (request, error) = match error {
31ce2453c82fatagen2026-09-24 13:07 UTC925
            mpsc::error::TrySendError::Full(request) => (
31ce2453c82fatagen2026-09-24 13:07 UTC926
                request,
31ce2453c82fatagen2026-09-24 13:07 UTC927
                Error::new(ErrorKind::RoomBusy, "room command queue is full"),
31ce2453c82fatagen2026-09-24 13:07 UTC928
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC929
            mpsc::error::TrySendError::Closed(request) => (
31ce2453c82fatagen2026-09-24 13:07 UTC930
                request,
31ce2453c82fatagen2026-09-24 13:07 UTC931
                Error::new(ErrorKind::RoomNotOpen, "room is not open"),
31ce2453c82fatagen2026-09-24 13:07 UTC932
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC933
        };
31ce2453c82fatagen2026-09-24 13:07 UTC934
        request.into_reply().done(Err(error));
31ce2453c82fatagen2026-09-24 13:07 UTC935
    }
31ce2453c82fatagen2026-09-24 13:07 UTC936
}
31ce2453c82fatagen2026-09-24 13:07 UTC937
31ce2453c82fatagen2026-09-24 13:07 UTC938
fn defer(pending: &mut VecDeque<Deferred>, deferred: Deferred, capacity: usize) {
31ce2453c82fatagen2026-09-24 13:07 UTC939
    if pending.len() < capacity {
31ce2453c82fatagen2026-09-24 13:07 UTC940
        return pending.push_back(deferred);
31ce2453c82fatagen2026-09-24 13:07 UTC941
    }
31ce2453c82fatagen2026-09-24 13:07 UTC942
    let (Deferred::Open(reply) | Deferred::Close(reply)) = deferred;
31ce2453c82fatagen2026-09-24 13:07 UTC943
    reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC944
        ErrorKind::RoomBusy,
31ce2453c82fatagen2026-09-24 13:07 UTC945
        "room lifecycle queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC946
    )));
31ce2453c82fatagen2026-09-24 13:07 UTC947
}
31ce2453c82fatagen2026-09-24 13:07 UTC948
673563a76e43atagen2026-09-26 12:30 UTC949
async fn settled(logouts: &mut watch::Receiver<Logouts>) {
673563a76e43atagen2026-09-26 12:30 UTC950
    let _ = logouts.wait_for(|logouts| logouts.in_flight == 0).await;
673563a76e43atagen2026-09-26 12:30 UTC951
}
673563a76e43atagen2026-09-26 12:30 UTC952
31ce2453c82fatagen2026-09-24 13:07 UTC953
fn enqueue<T>(sender: &mpsc::Sender<Job<T>>, job: Job<T>) {
31ce2453c82fatagen2026-09-24 13:07 UTC954
    if let Err(error) = sender.try_send(job) {
31ce2453c82fatagen2026-09-24 13:07 UTC955
        let (job, error) = match error {
31ce2453c82fatagen2026-09-24 13:07 UTC956
            mpsc::error::TrySendError::Full(job) => (
31ce2453c82fatagen2026-09-24 13:07 UTC957
                job,
31ce2453c82fatagen2026-09-24 13:07 UTC958
                Error::new(ErrorKind::QueueFull, "command queue is full"),
31ce2453c82fatagen2026-09-24 13:07 UTC959
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC960
            mpsc::error::TrySendError::Closed(job) => (job, Error::interrupted()),
31ce2453c82fatagen2026-09-24 13:07 UTC961
        };
31ce2453c82fatagen2026-09-24 13:07 UTC962
        job.reply.done(Err(error));
31ce2453c82fatagen2026-09-24 13:07 UTC963
    }
31ce2453c82fatagen2026-09-24 13:07 UTC964
}