atagen

macltok

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

Blame: src/engine/mod.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
mod commands;
cbb715e11963atagen2026-10-24 23:24 UTC2
pub(crate) mod context;
a319bae745d8atagen2026-09-26 09:59 UTC3
pub(crate) mod executor;
31ce2453c82fatagen2026-09-24 13:07 UTC4
mod media;
31ce2453c82fatagen2026-09-24 13:07 UTC5
mod mutations;
fa51490b6ae9atagen2026-10-24 08:41 UTC6
mod packs;
38c4caecdb3datagen2026-09-26 13:35 UTC7
mod prepare;
80381e06a573atagen2026-10-25 03:02 UTC8
mod repair;
31ce2453c82fatagen2026-09-24 13:07 UTC9
mod room;
f12377bce9b9atagen2026-09-26 12:32 UTC10
mod session;
66db1b3cd895atagen2026-10-25 10:28 UTC11
mod thread_list;
31ce2453c82fatagen2026-09-24 13:07 UTC12
mod timeline;
0291516d40b5atagen2026-10-22 08:31 UTC13
mod url_preview;
31ce2453c82fatagen2026-09-24 13:07 UTC14
mod watchers;
31ce2453c82fatagen2026-09-24 13:07 UTC15
31ce2453c82fatagen2026-09-24 13:07 UTC16
use std::{
31ce2453c82fatagen2026-09-24 13:07 UTC17
    collections::{HashMap, VecDeque},
31ce2453c82fatagen2026-09-24 13:07 UTC18
    future::Future,
1fe480c16a3catagen2026-09-26 08:44 UTC19
    panic::AssertUnwindSafe,
31ce2453c82fatagen2026-09-24 13:07 UTC20
    sync::Arc,
31ce2453c82fatagen2026-09-24 13:07 UTC21
};
31ce2453c82fatagen2026-09-24 13:07 UTC22
1fe480c16a3catagen2026-09-26 08:44 UTC23
use futures_util::FutureExt;
1fe480c16a3catagen2026-09-26 08:44 UTC24
use matrix_sdk::{Client, SendOutsideWasm, SessionChange};
31ce2453c82fatagen2026-09-24 13:07 UTC25
use matrix_sdk_ui::sync_service::SyncService;
673563a76e43atagen2026-09-26 12:30 UTC26
use ruma::{OwnedDeviceId, OwnedRoomId};
31ce2453c82fatagen2026-09-24 13:07 UTC27
use thiserror::Error as ThisError;
fe4133dbcd47atagen2026-09-26 09:05 UTC28
use tokio::sync::{broadcast, mpsc, watch};
31ce2453c82fatagen2026-09-24 13:07 UTC29
31ce2453c82fatagen2026-09-24 13:07 UTC30
pub(crate) use media::MediaService;
f12377bce9b9atagen2026-09-26 12:32 UTC31
pub(crate) use session::Persistence;
31ce2453c82fatagen2026-09-24 13:07 UTC32
a250c7fa43fbatagen2026-09-26 13:35 UTC33
#[cfg(any(feature = "sqlite", feature = "indexeddb"))]
a250c7fa43fbatagen2026-09-26 13:35 UTC34
pub(crate) use crate::store_lock::StoreLock;
a250c7fa43fbatagen2026-09-26 13:35 UTC35
#[cfg(not(any(feature = "sqlite", feature = "indexeddb")))]
a250c7fa43fbatagen2026-09-26 13:35 UTC36
pub(crate) enum StoreLock {}
a250c7fa43fbatagen2026-09-26 13:35 UTC37
cbb715e11963atagen2026-10-24 23:24 UTC38
#[cfg(any(feature = "sqlite", feature = "indexeddb"))]
cbb715e11963atagen2026-10-24 23:24 UTC39
pub(crate) use crate::mentions::store::MentionsStore;
cbb715e11963atagen2026-10-24 23:24 UTC40
#[cfg(not(any(feature = "sqlite", feature = "indexeddb")))]
cbb715e11963atagen2026-10-24 23:24 UTC41
pub(crate) enum MentionsStore {}
cbb715e11963atagen2026-10-24 23:24 UTC42
c9effeac326aatagen2026-10-25 10:32 UTC43
#[cfg(any(feature = "sqlite", feature = "indexeddb"))]
c9effeac326aatagen2026-10-25 10:32 UTC44
pub(crate) use crate::search::store::SearchStore;
c9effeac326aatagen2026-10-25 10:32 UTC45
#[cfg(not(any(feature = "sqlite", feature = "indexeddb")))]
c9effeac326aatagen2026-10-25 10:32 UTC46
pub(crate) enum SearchStore {}
c9effeac326aatagen2026-10-25 10:32 UTC47
31ce2453c82fatagen2026-09-24 13:07 UTC48
use self::{
31ce2453c82fatagen2026-09-24 13:07 UTC49
    commands::Job,
1fe480c16a3catagen2026-09-26 08:44 UTC50
    executor::{EngineBoxFuture, TaskSet},
31ce2453c82fatagen2026-09-24 13:07 UTC51
    mutations::{Mutation, MutationJob},
fa51490b6ae9atagen2026-10-24 08:41 UTC52
    packs::PackEdit,
5a52012c9b1datagen2026-09-24 23:45 UTC53
    room::{ActorNote, ActorRequest, RoomActor},
31ce2453c82fatagen2026-09-24 13:07 UTC54
    timeline::{SessionContext, Supervisor},
31ce2453c82fatagen2026-09-24 13:07 UTC55
};
31ce2453c82fatagen2026-09-24 13:07 UTC56
use crate::{
9bd34e25bb86atagen2026-10-24 07:23 UTC57
    AccountCommand, ClientHandle, Command, CommandOutput, ConnectionState, EncryptionCommand,
fa51490b6ae9atagen2026-10-24 08:41 UTC58
    EngineErrorKind, EngineFailure, Error, ErrorKind, Event, EventStream, ImagePackSource,
fa51490b6ae9atagen2026-10-24 08:41 UTC59
    LifecycleCommand, MediaCommand, NotificationCommand, PackCommand, RoomCommand, Termination,
66db1b3cd895atagen2026-10-25 10:28 UTC60
    ThreadListCommand, TimelineCommand,
0291516d40b5atagen2026-10-22 08:31 UTC61
    normalise::{ROOM_PACKS, ROOM_PREVIEW_URLS},
31ce2453c82fatagen2026-09-24 13:07 UTC62
    transport::{DriverPort, Request, Responder, transport_pair},
31ce2453c82fatagen2026-09-24 13:07 UTC63
};
31ce2453c82fatagen2026-09-24 13:07 UTC64
31ce2453c82fatagen2026-09-24 13:07 UTC65
#[derive(Clone, Debug, Eq, PartialEq)]
31ce2453c82fatagen2026-09-24 13:07 UTC66
#[non_exhaustive]
31ce2453c82fatagen2026-09-24 13:07 UTC67
pub struct EngineConfig {
31ce2453c82fatagen2026-09-24 13:07 UTC68
    pub command_capacity: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC69
    pub event_capacity: usize,
d9b96952ec60atagen2026-10-25 09:07 UTC70
    /// what a thread paginates as it opens, and a room if its recent load fails. 0 for neither
31ce2453c82fatagen2026-09-24 13:07 UTC71
    pub initial_history_limit: u16,
31ce2453c82fatagen2026-09-24 13:07 UTC72
    pub max_media_bytes: usize,
da8896938cf0atagen2026-10-25 10:04 UTC73
    /// media one event context session keeps registered, least recently used dropped first
da8896938cf0atagen2026-10-25 10:04 UTC74
    pub max_context_media: usize,
cbb715e11963atagen2026-10-24 23:24 UTC75
    pub max_mentions_bytes: usize,
cbb715e11963atagen2026-10-24 23:24 UTC76
    pub max_mentions_entries: usize,
cbb715e11963atagen2026-10-24 23:24 UTC77
    pub max_mention_context_events: usize,
cbb715e11963atagen2026-10-24 23:24 UTC78
    pub max_mention_candidates: usize,
38c4caecdb3datagen2026-09-26 13:35 UTC79
    /// warm a room up for sending when it's opened or someone starts typing in it
38c4caecdb3datagen2026-09-26 13:35 UTC80
    pub prepare_to_send: bool,
c9effeac326aatagen2026-10-25 10:32 UTC81
    /// `/messages` pages each joined room gets per engine run for search backfill. 0 for none
c9effeac326aatagen2026-10-25 10:32 UTC82
    pub search_backfill_pages_per_room: u16,
c9effeac326aatagen2026-10-25 10:32 UTC83
    pub search_backfill_page_size: u16,
c9effeac326aatagen2026-10-25 10:32 UTC84
    /// the least time between two backfill requests, across every room
c9effeac326aatagen2026-10-25 10:32 UTC85
    pub search_backfill_interval_ms: u64,
c9effeac326aatagen2026-10-25 10:32 UTC86
    /// undecryptable messages kept to index once their keys turn up, newest first
c9effeac326aatagen2026-10-25 10:32 UTC87
    pub max_search_undecrypted: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC88
}
31ce2453c82fatagen2026-09-24 13:07 UTC89
31ce2453c82fatagen2026-09-24 13:07 UTC90
impl EngineConfig {
31ce2453c82fatagen2026-09-24 13:07 UTC91
    #[must_use]
cbb715e11963atagen2026-10-24 23:24 UTC92
    pub const fn with_max_mentions_bytes(mut self, bytes: usize) -> Self {
cbb715e11963atagen2026-10-24 23:24 UTC93
        self.max_mentions_bytes = bytes;
cbb715e11963atagen2026-10-24 23:24 UTC94
        self
cbb715e11963atagen2026-10-24 23:24 UTC95
    }
cbb715e11963atagen2026-10-24 23:24 UTC96
cbb715e11963atagen2026-10-24 23:24 UTC97
    #[must_use]
cbb715e11963atagen2026-10-24 23:24 UTC98
    pub const fn with_max_mentions_entries(mut self, entries: usize) -> Self {
cbb715e11963atagen2026-10-24 23:24 UTC99
        self.max_mentions_entries = entries;
cbb715e11963atagen2026-10-24 23:24 UTC100
        self
cbb715e11963atagen2026-10-24 23:24 UTC101
    }
cbb715e11963atagen2026-10-24 23:24 UTC102
cbb715e11963atagen2026-10-24 23:24 UTC103
    #[must_use]
cbb715e11963atagen2026-10-24 23:24 UTC104
    pub const fn with_max_mention_context_events(mut self, events: usize) -> Self {
cbb715e11963atagen2026-10-24 23:24 UTC105
        self.max_mention_context_events = events;
cbb715e11963atagen2026-10-24 23:24 UTC106
        self
cbb715e11963atagen2026-10-24 23:24 UTC107
    }
cbb715e11963atagen2026-10-24 23:24 UTC108
cbb715e11963atagen2026-10-24 23:24 UTC109
    #[must_use]
cbb715e11963atagen2026-10-24 23:24 UTC110
    pub const fn with_max_mention_candidates(mut self, candidates: usize) -> Self {
cbb715e11963atagen2026-10-24 23:24 UTC111
        self.max_mention_candidates = candidates;
cbb715e11963atagen2026-10-24 23:24 UTC112
        self
cbb715e11963atagen2026-10-24 23:24 UTC113
    }
cbb715e11963atagen2026-10-24 23:24 UTC114
cbb715e11963atagen2026-10-24 23:24 UTC115
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC116
    pub const fn with_command_capacity(mut self, capacity: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC117
        self.command_capacity = capacity;
31ce2453c82fatagen2026-09-24 13:07 UTC118
        self
31ce2453c82fatagen2026-09-24 13:07 UTC119
    }
31ce2453c82fatagen2026-09-24 13:07 UTC120
31ce2453c82fatagen2026-09-24 13:07 UTC121
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC122
    pub const fn with_event_capacity(mut self, capacity: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC123
        self.event_capacity = capacity;
31ce2453c82fatagen2026-09-24 13:07 UTC124
        self
31ce2453c82fatagen2026-09-24 13:07 UTC125
    }
31ce2453c82fatagen2026-09-24 13:07 UTC126
31ce2453c82fatagen2026-09-24 13:07 UTC127
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC128
    pub const fn with_initial_history_limit(mut self, limit: u16) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC129
        self.initial_history_limit = limit;
31ce2453c82fatagen2026-09-24 13:07 UTC130
        self
31ce2453c82fatagen2026-09-24 13:07 UTC131
    }
31ce2453c82fatagen2026-09-24 13:07 UTC132
31ce2453c82fatagen2026-09-24 13:07 UTC133
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC134
    pub const fn with_max_media_bytes(mut self, bytes: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC135
        self.max_media_bytes = bytes;
31ce2453c82fatagen2026-09-24 13:07 UTC136
        self
31ce2453c82fatagen2026-09-24 13:07 UTC137
    }
31ce2453c82fatagen2026-09-24 13:07 UTC138
31ce2453c82fatagen2026-09-24 13:07 UTC139
    #[must_use]
da8896938cf0atagen2026-10-25 10:04 UTC140
    pub const fn with_max_context_media(mut self, media: usize) -> Self {
da8896938cf0atagen2026-10-25 10:04 UTC141
        self.max_context_media = media;
31ce2453c82fatagen2026-09-24 13:07 UTC142
        self
31ce2453c82fatagen2026-09-24 13:07 UTC143
    }
38c4caecdb3datagen2026-09-26 13:35 UTC144
38c4caecdb3datagen2026-09-26 13:35 UTC145
    #[must_use]
38c4caecdb3datagen2026-09-26 13:35 UTC146
    pub const fn with_prepare_to_send(mut self, prepare: bool) -> Self {
38c4caecdb3datagen2026-09-26 13:35 UTC147
        self.prepare_to_send = prepare;
38c4caecdb3datagen2026-09-26 13:35 UTC148
        self
38c4caecdb3datagen2026-09-26 13:35 UTC149
    }
c9effeac326aatagen2026-10-25 10:32 UTC150
c9effeac326aatagen2026-10-25 10:32 UTC151
    #[must_use]
c9effeac326aatagen2026-10-25 10:32 UTC152
    pub const fn with_search_backfill_pages_per_room(mut self, pages: u16) -> Self {
c9effeac326aatagen2026-10-25 10:32 UTC153
        self.search_backfill_pages_per_room = pages;
c9effeac326aatagen2026-10-25 10:32 UTC154
        self
c9effeac326aatagen2026-10-25 10:32 UTC155
    }
c9effeac326aatagen2026-10-25 10:32 UTC156
c9effeac326aatagen2026-10-25 10:32 UTC157
    #[must_use]
c9effeac326aatagen2026-10-25 10:32 UTC158
    pub const fn with_search_backfill_page_size(mut self, events: u16) -> Self {
c9effeac326aatagen2026-10-25 10:32 UTC159
        self.search_backfill_page_size = events;
c9effeac326aatagen2026-10-25 10:32 UTC160
        self
c9effeac326aatagen2026-10-25 10:32 UTC161
    }
c9effeac326aatagen2026-10-25 10:32 UTC162
c9effeac326aatagen2026-10-25 10:32 UTC163
    #[must_use]
c9effeac326aatagen2026-10-25 10:32 UTC164
    pub const fn with_search_backfill_interval_ms(mut self, interval_ms: u64) -> Self {
c9effeac326aatagen2026-10-25 10:32 UTC165
        self.search_backfill_interval_ms = interval_ms;
c9effeac326aatagen2026-10-25 10:32 UTC166
        self
c9effeac326aatagen2026-10-25 10:32 UTC167
    }
c9effeac326aatagen2026-10-25 10:32 UTC168
c9effeac326aatagen2026-10-25 10:32 UTC169
    #[must_use]
c9effeac326aatagen2026-10-25 10:32 UTC170
    pub const fn with_max_search_undecrypted(mut self, events: usize) -> Self {
c9effeac326aatagen2026-10-25 10:32 UTC171
        self.max_search_undecrypted = events;
c9effeac326aatagen2026-10-25 10:32 UTC172
        self
c9effeac326aatagen2026-10-25 10:32 UTC173
    }
31ce2453c82fatagen2026-09-24 13:07 UTC174
}
31ce2453c82fatagen2026-09-24 13:07 UTC175
31ce2453c82fatagen2026-09-24 13:07 UTC176
impl Default for EngineConfig {
31ce2453c82fatagen2026-09-24 13:07 UTC177
    fn default() -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC178
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC179
            command_capacity: 64,
31ce2453c82fatagen2026-09-24 13:07 UTC180
            event_capacity: 128,
31ce2453c82fatagen2026-09-24 13:07 UTC181
            initial_history_limit: 30,
31ce2453c82fatagen2026-09-24 13:07 UTC182
            max_media_bytes: 20 * 1024 * 1024,
da8896938cf0atagen2026-10-25 10:04 UTC183
            max_context_media: 1_024,
cbb715e11963atagen2026-10-24 23:24 UTC184
            max_mentions_bytes: 64 * 1024 * 1024,
cbb715e11963atagen2026-10-24 23:24 UTC185
            max_mentions_entries: 5_000,
cbb715e11963atagen2026-10-24 23:24 UTC186
            max_mention_context_events: 25_000,
cbb715e11963atagen2026-10-24 23:24 UTC187
            max_mention_candidates: 5_000,
38c4caecdb3datagen2026-09-26 13:35 UTC188
            prepare_to_send: true,
c9effeac326aatagen2026-10-25 10:32 UTC189
            search_backfill_pages_per_room: 10,
c9effeac326aatagen2026-10-25 10:32 UTC190
            search_backfill_page_size: 100,
c9effeac326aatagen2026-10-25 10:32 UTC191
            search_backfill_interval_ms: 2_000,
c9effeac326aatagen2026-10-25 10:32 UTC192
            max_search_undecrypted: 2_000,
31ce2453c82fatagen2026-09-24 13:07 UTC193
        }
31ce2453c82fatagen2026-09-24 13:07 UTC194
    }
31ce2453c82fatagen2026-09-24 13:07 UTC195
}
31ce2453c82fatagen2026-09-24 13:07 UTC196
31ce2453c82fatagen2026-09-24 13:07 UTC197
#[derive(Debug, ThisError)]
31ce2453c82fatagen2026-09-24 13:07 UTC198
pub enum EngineError {
31ce2453c82fatagen2026-09-24 13:07 UTC199
    #[error("the Matrix client is not authenticated")]
31ce2453c82fatagen2026-09-24 13:07 UTC200
    Unauthenticated,
cbb715e11963atagen2026-10-24 23:24 UTC201
    #[error("command, event, media source, and mentions capacities must be nonzero")]
31ce2453c82fatagen2026-09-24 13:07 UTC202
    ZeroCapacity,
31ce2453c82fatagen2026-09-24 13:07 UTC203
    #[error("could not build the sync service")]
31ce2453c82fatagen2026-09-24 13:07 UTC204
    SyncService(#[source] matrix_sdk_ui::sync_service::Error),
31ce2453c82fatagen2026-09-24 13:07 UTC205
    #[error("the event receiver has closed")]
31ce2453c82fatagen2026-09-24 13:07 UTC206
    EventReceiverClosed,
31ce2453c82fatagen2026-09-24 13:07 UTC207
    #[error("engine task {task} panicked")]
31ce2453c82fatagen2026-09-24 13:07 UTC208
    TaskPanicked { task: String },
31ce2453c82fatagen2026-09-24 13:07 UTC209
}
31ce2453c82fatagen2026-09-24 13:07 UTC210
31ce2453c82fatagen2026-09-24 13:07 UTC211
impl EngineError {
31ce2453c82fatagen2026-09-24 13:07 UTC212
    fn failure(&self) -> EngineFailure {
31ce2453c82fatagen2026-09-24 13:07 UTC213
        EngineFailure {
31ce2453c82fatagen2026-09-24 13:07 UTC214
            kind: match self {
31ce2453c82fatagen2026-09-24 13:07 UTC215
                Self::EventReceiverClosed => EngineErrorKind::EventReceiverClosed,
31ce2453c82fatagen2026-09-24 13:07 UTC216
                Self::TaskPanicked { .. } => EngineErrorKind::Watcher,
913832a327a8atagen2026-10-25 09:33 UTC217
                Self::Unauthenticated | Self::ZeroCapacity | Self::SyncService(_) => {
913832a327a8atagen2026-10-25 09:33 UTC218
                    EngineErrorKind::Other
913832a327a8atagen2026-10-25 09:33 UTC219
                }
31ce2453c82fatagen2026-09-24 13:07 UTC220
            },
31ce2453c82fatagen2026-09-24 13:07 UTC221
            message: self.to_string(),
31ce2453c82fatagen2026-09-24 13:07 UTC222
        }
31ce2453c82fatagen2026-09-24 13:07 UTC223
    }
31ce2453c82fatagen2026-09-24 13:07 UTC224
}
31ce2453c82fatagen2026-09-24 13:07 UTC225
31ce2453c82fatagen2026-09-24 13:07 UTC226
#[derive(Clone)]
31ce2453c82fatagen2026-09-24 13:07 UTC227
enum TaskContext {
31ce2453c82fatagen2026-09-24 13:07 UTC228
    /// a panic here costs the caller an `Interrupted` answer and nothing else
31ce2453c82fatagen2026-09-24 13:07 UTC229
    Command(&'static str),
31ce2453c82fatagen2026-09-24 13:07 UTC230
    /// a panic here takes the engine with it
31ce2453c82fatagen2026-09-24 13:07 UTC231
    Watcher(&'static str),
31ce2453c82fatagen2026-09-24 13:07 UTC232
    RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC233
        room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC234
        generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC235
    },
31ce2453c82fatagen2026-09-24 13:07 UTC236
}
31ce2453c82fatagen2026-09-24 13:07 UTC237
31ce2453c82fatagen2026-09-24 13:07 UTC238
impl TaskContext {
31ce2453c82fatagen2026-09-24 13:07 UTC239
    fn label(&self) -> String {
31ce2453c82fatagen2026-09-24 13:07 UTC240
        match self {
31ce2453c82fatagen2026-09-24 13:07 UTC241
            Self::Command(name) | Self::Watcher(name) => (*name).to_owned(),
31ce2453c82fatagen2026-09-24 13:07 UTC242
            Self::RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC243
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC244
                generation,
31ce2453c82fatagen2026-09-24 13:07 UTC245
            } => format!("room actor {room_id} generation {generation}"),
31ce2453c82fatagen2026-09-24 13:07 UTC246
        }
31ce2453c82fatagen2026-09-24 13:07 UTC247
    }
31ce2453c82fatagen2026-09-24 13:07 UTC248
}
31ce2453c82fatagen2026-09-24 13:07 UTC249
6a5963d5bd63atagen2026-10-28 05:03 UTC250
/// tuwunel resends every room in range each time the room list grows, so the sdk's 100 a step
6a5963d5bd63atagen2026-10-28 05:03 UTC251
/// had a 399 room account hand most rooms to the event cache four or five times
6a5963d5bd63atagen2026-10-28 05:03 UTC252
const ROOM_LIST_GROWING_BATCH: u32 = 500;
6a5963d5bd63atagen2026-10-28 05:03 UTC253
a912b21d0811atagen2026-10-28 07:52 UTC254
/// how long shutdown waits on the mentions and search services before aborting them with the
a912b21d0811atagen2026-10-28 07:52 UTC255
/// other tasks. a sync response still being processed kept mentions busy for 2 to 3.4s, and the
a912b21d0811atagen2026-10-28 07:52 UTC256
/// next engine can't start until this one is gone
a912b21d0811atagen2026-10-28 07:52 UTC257
const SERVICE_STOP_GRACE: std::time::Duration = std::time::Duration::from_millis(500);
a912b21d0811atagen2026-10-28 07:52 UTC258
a912b21d0811atagen2026-10-28 07:52 UTC259
async fn stop_within(stop: impl Future<Output = ()>, service: &'static str) {
a912b21d0811atagen2026-10-28 07:52 UTC260
    if matrix_sdk::timeout::timeout(stop, SERVICE_STOP_GRACE)
a912b21d0811atagen2026-10-28 07:52 UTC261
        .await
a912b21d0811atagen2026-10-28 07:52 UTC262
        .is_err()
a912b21d0811atagen2026-10-28 07:52 UTC263
    {
a912b21d0811atagen2026-10-28 07:52 UTC264
        tracing::debug!(service, "a service didn't stop in time and is aborted");
a912b21d0811atagen2026-10-28 07:52 UTC265
    }
a912b21d0811atagen2026-10-28 07:52 UTC266
}
a912b21d0811atagen2026-10-28 07:52 UTC267
7b376d620a3datagen2026-09-26 12:53 UTC268
/// tasks catch their own panics so each comes back with its context, since joining
7b376d620a3datagen2026-09-26 12:53 UTC269
/// through the sdk's executor gives no task id to look one up by. wasm has nothing to catch
1fe480c16a3catagen2026-09-26 08:44 UTC270
fn panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
1fe480c16a3catagen2026-09-26 08:44 UTC271
    payload
1fe480c16a3catagen2026-09-26 08:44 UTC272
        .downcast_ref::<&str>()
1fe480c16a3catagen2026-09-26 08:44 UTC273
        .map(ToString::to_string)
1fe480c16a3catagen2026-09-26 08:44 UTC274
        .or_else(|| payload.downcast_ref::<String>().cloned())
1fe480c16a3catagen2026-09-26 08:44 UTC275
        .unwrap_or_else(|| "task panicked with a non-string payload".to_owned())
1fe480c16a3catagen2026-09-26 08:44 UTC276
}
1fe480c16a3catagen2026-09-26 08:44 UTC277
31ce2453c82fatagen2026-09-24 13:07 UTC278
struct TaskRegistry {
1fe480c16a3catagen2026-09-26 08:44 UTC279
    tasks: TaskSet<(TaskContext, Option<String>)>,
31ce2453c82fatagen2026-09-24 13:07 UTC280
}
31ce2453c82fatagen2026-09-24 13:07 UTC281
31ce2453c82fatagen2026-09-24 13:07 UTC282
impl TaskRegistry {
31ce2453c82fatagen2026-09-24 13:07 UTC283
    fn spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC284
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC285
        context: TaskContext,
1fe480c16a3catagen2026-09-26 08:44 UTC286
        task: impl Future<Output = ()> + SendOutsideWasm + 'static,
1fe480c16a3catagen2026-09-26 08:44 UTC287
    ) {
1fe480c16a3catagen2026-09-26 08:44 UTC288
        self.tasks.spawn(async move {
1fe480c16a3catagen2026-09-26 08:44 UTC289
            let panic = AssertUnwindSafe(task)
1fe480c16a3catagen2026-09-26 08:44 UTC290
                .catch_unwind()
1fe480c16a3catagen2026-09-26 08:44 UTC291
                .await
1fe480c16a3catagen2026-09-26 08:44 UTC292
                .err()
1fe480c16a3catagen2026-09-26 08:44 UTC293
                .map(panic_message);
1fe480c16a3catagen2026-09-26 08:44 UTC294
            (context, panic)
1fe480c16a3catagen2026-09-26 08:44 UTC295
        });
31ce2453c82fatagen2026-09-24 13:07 UTC296
    }
31ce2453c82fatagen2026-09-24 13:07 UTC297
1fe480c16a3catagen2026-09-26 08:44 UTC298
    fn is_empty(&self) -> bool {
1fe480c16a3catagen2026-09-26 08:44 UTC299
        self.tasks.is_empty()
1fe480c16a3catagen2026-09-26 08:44 UTC300
    }
1fe480c16a3catagen2026-09-26 08:44 UTC301
1fe480c16a3catagen2026-09-26 08:44 UTC302
    async fn join_next(&mut self) -> Option<(TaskContext, Option<String>)> {
1fe480c16a3catagen2026-09-26 08:44 UTC303
        loop {
1fe480c16a3catagen2026-09-26 08:44 UTC304
            if let Ok(done) = self.tasks.join_next().await? {
1fe480c16a3catagen2026-09-26 08:44 UTC305
                return Some(done);
1fe480c16a3catagen2026-09-26 08:44 UTC306
            }
1fe480c16a3catagen2026-09-26 08:44 UTC307
        }
31ce2453c82fatagen2026-09-24 13:07 UTC308
    }
31ce2453c82fatagen2026-09-24 13:07 UTC309
31ce2453c82fatagen2026-09-24 13:07 UTC310
    async fn shutdown(&mut self) {
1fe480c16a3catagen2026-09-26 08:44 UTC311
        self.tasks.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC312
    }
31ce2453c82fatagen2026-09-24 13:07 UTC313
}
31ce2453c82fatagen2026-09-24 13:07 UTC314
31ce2453c82fatagen2026-09-24 13:07 UTC315
enum RoomSlot {
31ce2453c82fatagen2026-09-24 13:07 UTC316
    Active {
31ce2453c82fatagen2026-09-24 13:07 UTC317
        generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC318
        sender: mpsc::Sender<ActorRequest>,
31ce2453c82fatagen2026-09-24 13:07 UTC319
    },
31ce2453c82fatagen2026-09-24 13:07 UTC320
    /// opens and closes that arrive while the actor winds down, replayed in order once it's gone
31ce2453c82fatagen2026-09-24 13:07 UTC321
    Closing {
31ce2453c82fatagen2026-09-24 13:07 UTC322
        generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC323
        pending: VecDeque<Deferred>,
31ce2453c82fatagen2026-09-24 13:07 UTC324
    },
31ce2453c82fatagen2026-09-24 13:07 UTC325
}
31ce2453c82fatagen2026-09-24 13:07 UTC326
31ce2453c82fatagen2026-09-24 13:07 UTC327
impl RoomSlot {
31ce2453c82fatagen2026-09-24 13:07 UTC328
    const fn generation(&self) -> u64 {
31ce2453c82fatagen2026-09-24 13:07 UTC329
        match self {
31ce2453c82fatagen2026-09-24 13:07 UTC330
            Self::Active { generation, .. } | Self::Closing { generation, .. } => *generation,
31ce2453c82fatagen2026-09-24 13:07 UTC331
        }
31ce2453c82fatagen2026-09-24 13:07 UTC332
    }
31ce2453c82fatagen2026-09-24 13:07 UTC333
}
31ce2453c82fatagen2026-09-24 13:07 UTC334
31ce2453c82fatagen2026-09-24 13:07 UTC335
enum Deferred {
31ce2453c82fatagen2026-09-24 13:07 UTC336
    Open(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC337
    Close(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC338
}
31ce2453c82fatagen2026-09-24 13:07 UTC339
673563a76e43atagen2026-09-26 12:30 UTC340
/// logouts in flight, and a token rejection held back because one of them caused it
673563a76e43atagen2026-09-26 12:30 UTC341
#[derive(Default)]
673563a76e43atagen2026-09-26 12:30 UTC342
pub(super) struct Logouts {
673563a76e43atagen2026-09-26 12:30 UTC343
    pub(super) in_flight: usize,
673563a76e43atagen2026-09-26 12:30 UTC344
    pub(super) rejected: Option<Option<OwnedDeviceId>>,
673563a76e43atagen2026-09-26 12:30 UTC345
}
673563a76e43atagen2026-09-26 12:30 UTC346
31ce2453c82fatagen2026-09-24 13:07 UTC347
struct Queues {
31ce2453c82fatagen2026-09-24 13:07 UTC348
    mutations: mpsc::Receiver<MutationJob>,
31ce2453c82fatagen2026-09-24 13:07 UTC349
    notifications: mpsc::Receiver<Job<NotificationCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC350
    encryption: mpsc::Receiver<Job<EncryptionCommand>>,
66db1b3cd895atagen2026-10-25 10:28 UTC351
    thread_lists: mpsc::Receiver<Job<ThreadListCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC352
    session_changes: broadcast::Receiver<SessionChange>,
f12377bce9b9atagen2026-09-26 12:32 UTC353
    persistence: Option<Persistence>,
31ce2453c82fatagen2026-09-24 13:07 UTC354
}
31ce2453c82fatagen2026-09-24 13:07 UTC355
5a52012c9b1datagen2026-09-24 23:45 UTC356
/// requests other devices send us, handed from the sdk's event handler to the encryption worker
5a52012c9b1datagen2026-09-24 23:45 UTC357
type IncomingVerifications =
5a52012c9b1datagen2026-09-24 23:45 UTC358
    mpsc::UnboundedSender<matrix_sdk::encryption::verification::VerificationRequest>;
5a52012c9b1datagen2026-09-24 23:45 UTC359
31ce2453c82fatagen2026-09-24 13:07 UTC360
pub struct Engine {
31ce2453c82fatagen2026-09-24 13:07 UTC361
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC362
    sync: Arc<SyncService>,
31ce2453c82fatagen2026-09-24 13:07 UTC363
    port: DriverPort,
31ce2453c82fatagen2026-09-24 13:07 UTC364
    config: EngineConfig,
31ce2453c82fatagen2026-09-24 13:07 UTC365
    media: Arc<MediaService>,
da8896938cf0atagen2026-10-25 10:04 UTC366
    contexts: Arc<context::Sessions>,
31ce2453c82fatagen2026-09-24 13:07 UTC367
    tasks: TaskRegistry,
31ce2453c82fatagen2026-09-24 13:07 UTC368
    rooms: HashMap<OwnedRoomId, RoomSlot>,
31ce2453c82fatagen2026-09-24 13:07 UTC369
    next_generation: u64,
5a52012c9b1datagen2026-09-24 23:45 UTC370
    notes: mpsc::UnboundedReceiver<ActorNote>,
5a52012c9b1datagen2026-09-24 23:45 UTC371
    note_sender: mpsc::UnboundedSender<ActorNote>,
5a52012c9b1datagen2026-09-24 23:45 UTC372
    /// the rooms sliding sync should follow, applied off the run loop since it touches the store
5a52012c9b1datagen2026-09-24 23:45 UTC373
    subscriptions: watch::Sender<Vec<OwnedRoomId>>,
31ce2453c82fatagen2026-09-24 13:07 UTC374
    mutations: mpsc::Sender<MutationJob>,
31ce2453c82fatagen2026-09-24 13:07 UTC375
    notifications: mpsc::Sender<Job<NotificationCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC376
    encryption: mpsc::Sender<Job<EncryptionCommand>>,
66db1b3cd895atagen2026-10-25 10:28 UTC377
    thread_lists: mpsc::Sender<Job<ThreadListCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC378
    queues: Option<Queues>,
5a52012c9b1datagen2026-09-24 23:45 UTC379
    /// the client can outlive us, so the handler has to come off it when we stop
5a52012c9b1datagen2026-09-24 23:45 UTC380
    verification_handler: Option<matrix_sdk::event_handler::EventHandlerHandle>,
fe4133dbcd47atagen2026-09-26 09:05 UTC381
    stop: mpsc::UnboundedSender<Termination>,
fe4133dbcd47atagen2026-09-26 09:05 UTC382
    stops: mpsc::UnboundedReceiver<Termination>,
673563a76e43atagen2026-09-26 12:30 UTC383
    logouts: Arc<watch::Sender<Logouts>>,
38c4caecdb3datagen2026-09-26 13:35 UTC384
    preparations: prepare::Preparations,
a250c7fa43fbatagen2026-09-26 13:35 UTC385
    /// shared with every `ClientHandle`, so the store stays locked while the client is reachable
a250c7fa43fbatagen2026-09-26 13:35 UTC386
    _store_lock: Option<Arc<StoreLock>>,
cbb715e11963atagen2026-10-24 23:24 UTC387
    mentions: crate::mentions::service::Handle,
cbb715e11963atagen2026-10-24 23:24 UTC388
    mentions_service: Option<crate::mentions::service::Service>,
cbb715e11963atagen2026-10-24 23:24 UTC389
    mentions_updates: Option<watch::Receiver<crate::MentionsPage>>,
c9effeac326aatagen2026-10-25 10:32 UTC390
    search: crate::search::Handle,
c9effeac326aatagen2026-10-25 10:32 UTC391
    search_service: Option<crate::search::Service>,
31ce2453c82fatagen2026-09-24 13:07 UTC392
}
31ce2453c82fatagen2026-09-24 13:07 UTC393
31ce2453c82fatagen2026-09-24 13:07 UTC394
impl Engine {
31ce2453c82fatagen2026-09-24 13:07 UTC395
    /// the escape hatch for a client you built yourself. `Session` is the usual way in
31ce2453c82fatagen2026-09-24 13:07 UTC396
    pub async fn from_client(
31ce2453c82fatagen2026-09-24 13:07 UTC397
        client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC398
        config: EngineConfig,
31ce2453c82fatagen2026-09-24 13:07 UTC399
    ) -> Result<(Self, ClientHandle, EventStream), EngineError> {
f12377bce9b9atagen2026-09-26 12:32 UTC400
        let session_changes = client.subscribe_to_session_changes();
c9effeac326aatagen2026-10-25 10:32 UTC401
        Self::with_persistence(client, config, session_changes, None, None, None, None).await
f12377bce9b9atagen2026-09-26 12:32 UTC402
    }
f12377bce9b9atagen2026-09-26 12:32 UTC403
f12377bce9b9atagen2026-09-26 12:32 UTC404
    /// `session_changes` is taken when the client is built, since the sdk can refresh
f12377bce9b9atagen2026-09-26 12:32 UTC405
    /// before an engine exists
f12377bce9b9atagen2026-09-26 12:32 UTC406
    pub(crate) async fn with_persistence(
f12377bce9b9atagen2026-09-26 12:32 UTC407
        client: Client,
f12377bce9b9atagen2026-09-26 12:32 UTC408
        config: EngineConfig,
f12377bce9b9atagen2026-09-26 12:32 UTC409
        session_changes: broadcast::Receiver<SessionChange>,
f12377bce9b9atagen2026-09-26 12:32 UTC410
        persistence: Option<Persistence>,
a250c7fa43fbatagen2026-09-26 13:35 UTC411
        store_lock: Option<StoreLock>,
82d37c369846atagen2026-10-25 10:55 UTC412
        mentions: Option<Result<MentionsStore, crate::mentions::service::Unopened>>,
c9effeac326aatagen2026-10-25 10:32 UTC413
        search: Option<Result<SearchStore, crate::Error>>,
f12377bce9b9atagen2026-09-26 12:32 UTC414
    ) -> Result<(Self, ClientHandle, EventStream), EngineError> {
31ce2453c82fatagen2026-09-24 13:07 UTC415
        if client.user_id().is_none() {
31ce2453c82fatagen2026-09-24 13:07 UTC416
            return Err(EngineError::Unauthenticated);
31ce2453c82fatagen2026-09-24 13:07 UTC417
        }
31ce2453c82fatagen2026-09-24 13:07 UTC418
        if config.command_capacity == 0
31ce2453c82fatagen2026-09-24 13:07 UTC419
            || config.event_capacity == 0
da8896938cf0atagen2026-10-25 10:04 UTC420
            || config.max_context_media == 0
cbb715e11963atagen2026-10-24 23:24 UTC421
            || config.max_mentions_entries == 0
cbb715e11963atagen2026-10-24 23:24 UTC422
            || config.max_mention_context_events == 0
cbb715e11963atagen2026-10-24 23:24 UTC423
            || config.max_mention_candidates == 0
31ce2453c82fatagen2026-09-24 13:07 UTC424
        {
31ce2453c82fatagen2026-09-24 13:07 UTC425
            return Err(EngineError::ZeroCapacity);
31ce2453c82fatagen2026-09-24 13:07 UTC426
        }
cbb715e11963atagen2026-10-24 23:24 UTC427
        let store_lock = store_lock.map(Arc::new);
cbb715e11963atagen2026-10-24 23:24 UTC428
        let media = Arc::new(MediaService::new(
cbb715e11963atagen2026-10-24 23:24 UTC429
            client.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC430
            config.max_media_bytes,
da8896938cf0atagen2026-10-25 10:04 UTC431
            config.max_context_media,
cbb715e11963atagen2026-10-24 23:24 UTC432
        ));
cbb715e11963atagen2026-10-24 23:24 UTC433
        let (mentions, mentions_service, mentions_updates) =
cbb715e11963atagen2026-10-24 23:24 UTC434
            crate::mentions::service::Service::new(
cbb715e11963atagen2026-10-24 23:24 UTC435
                client.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC436
                media.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC437
                mentions,
cbb715e11963atagen2026-10-24 23:24 UTC438
                config.clone(),
cbb715e11963atagen2026-10-24 23:24 UTC439
            )
cbb715e11963atagen2026-10-24 23:24 UTC440
            .await
0d763e4284efatagen2026-10-25 09:31 UTC441
            // a missing user id is all it still refuses, and that was turned away above
0d763e4284efatagen2026-10-25 09:31 UTC442
            .map_err(|_| EngineError::Unauthenticated)?;
31ce2453c82fatagen2026-09-24 13:07 UTC443
        let sync = Arc::new(
31ce2453c82fatagen2026-09-24 13:07 UTC444
            SyncService::builder(client.clone())
31ce2453c82fatagen2026-09-24 13:07 UTC445
                .with_offline_mode()
0291516d40b5atagen2026-10-22 08:31 UTC446
                .with_room_list_extra_required_state(vec![
0291516d40b5atagen2026-10-22 08:31 UTC447
                    (ROOM_PACKS.into(), "*".to_owned()),
0291516d40b5atagen2026-10-22 08:31 UTC448
                    (ROOM_PREVIEW_URLS.into(), String::new()),
0291516d40b5atagen2026-10-22 08:31 UTC449
                ])
cbb715e11963atagen2026-10-24 23:24 UTC450
                .with_room_list_response_commit_hook(mentions.hook())
6a5963d5bd63atagen2026-10-28 05:03 UTC451
                .with_room_list_growing_batch_size(ROOM_LIST_GROWING_BATCH)
31ce2453c82fatagen2026-09-24 13:07 UTC452
                .build()
31ce2453c82fatagen2026-09-24 13:07 UTC453
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC454
                .map_err(EngineError::SyncService)?,
31ce2453c82fatagen2026-09-24 13:07 UTC455
        );
31ce2453c82fatagen2026-09-24 13:07 UTC456
        let (handle, port, events) = transport_pair(
31ce2453c82fatagen2026-09-24 13:07 UTC457
            client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC458
            sync.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC459
            media.clone(),
a250c7fa43fbatagen2026-09-26 13:35 UTC460
            store_lock.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC461
            config.command_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC462
            config.event_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC463
        );
74befbc72bafatagen2026-10-25 11:18 UTC464
        let (search, search_service) = crate::search::Service::new(
74befbc72bafatagen2026-10-25 11:18 UTC465
            client.clone(),
74befbc72bafatagen2026-10-25 11:18 UTC466
            media.clone(),
74befbc72bafatagen2026-10-25 11:18 UTC467
            search,
74befbc72bafatagen2026-10-25 11:18 UTC468
            port.event_sender(),
74befbc72bafatagen2026-10-25 11:18 UTC469
            &config,
74befbc72bafatagen2026-10-25 11:18 UTC470
        );
31ce2453c82fatagen2026-09-24 13:07 UTC471
        client.send_queue().enable_upload_progress(true);
411bc1c78b65atagen2026-10-22 07:39 UTC472
        client.send_queue().enable_global_upload_progress(true);
5a52012c9b1datagen2026-09-24 23:45 UTC473
        let (note_sender, notes) = mpsc::unbounded_channel();
fe4133dbcd47atagen2026-09-26 09:05 UTC474
        let (stop, stops) = mpsc::unbounded_channel();
31ce2453c82fatagen2026-09-24 13:07 UTC475
        let (mutations, mutation_queue) = mpsc::channel(config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC476
        let (notifications, notification_queue) = mpsc::channel(config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC477
        let (encryption, encryption_queue) = mpsc::channel(config.command_capacity);
66db1b3cd895atagen2026-10-25 10:28 UTC478
        let (thread_lists, thread_list_queue) = mpsc::channel(config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC479
        let engine = Self {
1f875b103b27atagen2026-10-25 10:44 UTC480
            contexts: Arc::new(context::Sessions::new(port.event_sender())),
31ce2453c82fatagen2026-09-24 13:07 UTC481
            client,
31ce2453c82fatagen2026-09-24 13:07 UTC482
            sync,
31ce2453c82fatagen2026-09-24 13:07 UTC483
            port,
31ce2453c82fatagen2026-09-24 13:07 UTC484
            config,
31ce2453c82fatagen2026-09-24 13:07 UTC485
            media,
31ce2453c82fatagen2026-09-24 13:07 UTC486
            tasks: TaskRegistry {
1fe480c16a3catagen2026-09-26 08:44 UTC487
                tasks: TaskSet::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC488
            },
31ce2453c82fatagen2026-09-24 13:07 UTC489
            rooms: HashMap::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC490
            next_generation: 0,
5a52012c9b1datagen2026-09-24 23:45 UTC491
            notes,
5a52012c9b1datagen2026-09-24 23:45 UTC492
            note_sender,
5a52012c9b1datagen2026-09-24 23:45 UTC493
            subscriptions: watch::Sender::new(Vec::new()),
31ce2453c82fatagen2026-09-24 13:07 UTC494
            mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC495
            notifications,
31ce2453c82fatagen2026-09-24 13:07 UTC496
            encryption,
66db1b3cd895atagen2026-10-25 10:28 UTC497
            thread_lists,
31ce2453c82fatagen2026-09-24 13:07 UTC498
            queues: Some(Queues {
31ce2453c82fatagen2026-09-24 13:07 UTC499
                mutations: mutation_queue,
31ce2453c82fatagen2026-09-24 13:07 UTC500
                notifications: notification_queue,
31ce2453c82fatagen2026-09-24 13:07 UTC501
                encryption: encryption_queue,
66db1b3cd895atagen2026-10-25 10:28 UTC502
                thread_lists: thread_list_queue,
31ce2453c82fatagen2026-09-24 13:07 UTC503
                session_changes,
f12377bce9b9atagen2026-09-26 12:32 UTC504
                persistence,
31ce2453c82fatagen2026-09-24 13:07 UTC505
            }),
5a52012c9b1datagen2026-09-24 23:45 UTC506
            verification_handler: None,
38c4caecdb3datagen2026-09-26 13:35 UTC507
            preparations: prepare::Preparations::default(),
fe4133dbcd47atagen2026-09-26 09:05 UTC508
            stop,
fe4133dbcd47atagen2026-09-26 09:05 UTC509
            stops,
673563a76e43atagen2026-09-26 12:30 UTC510
            logouts: Arc::new(watch::Sender::new(Logouts::default())),
a250c7fa43fbatagen2026-09-26 13:35 UTC511
            _store_lock: store_lock,
cbb715e11963atagen2026-10-24 23:24 UTC512
            mentions,
cbb715e11963atagen2026-10-24 23:24 UTC513
            mentions_service: Some(mentions_service),
cbb715e11963atagen2026-10-24 23:24 UTC514
            mentions_updates: Some(mentions_updates),
c9effeac326aatagen2026-10-25 10:32 UTC515
            search,
c9effeac326aatagen2026-10-25 10:32 UTC516
            search_service: Some(search_service),
31ce2453c82fatagen2026-09-24 13:07 UTC517
        };
31ce2453c82fatagen2026-09-24 13:07 UTC518
        Ok((engine, handle, events))
31ce2453c82fatagen2026-09-24 13:07 UTC519
    }
31ce2453c82fatagen2026-09-24 13:07 UTC520
5a52012c9b1datagen2026-09-24 23:45 UTC521
    /// boxed because the engine's future is deep enough to blow the compiler's query depth
5a52012c9b1datagen2026-09-24 23:45 UTC522
    /// in every crate that spawns it, which isn't something a client should have to fix
fe4133dbcd47atagen2026-09-26 09:05 UTC523
    pub fn run(self) -> EngineBoxFuture<'static, Result<Termination, EngineError>> {
5a52012c9b1datagen2026-09-24 23:45 UTC524
        Box::pin(self.run_inner())
5a52012c9b1datagen2026-09-24 23:45 UTC525
    }
5a52012c9b1datagen2026-09-24 23:45 UTC526
fe4133dbcd47atagen2026-09-26 09:05 UTC527
    async fn run_inner(mut self) -> Result<Termination, EngineError> {
31ce2453c82fatagen2026-09-24 13:07 UTC528
        let events = self.port.event_sender();
31ce2453c82fatagen2026-09-24 13:07 UTC529
        let user_id = self
31ce2453c82fatagen2026-09-24 13:07 UTC530
            .client
31ce2453c82fatagen2026-09-24 13:07 UTC531
            .user_id()
31ce2453c82fatagen2026-09-24 13:07 UTC532
            .expect("authentication was checked during construction")
31ce2453c82fatagen2026-09-24 13:07 UTC533
            .to_owned();
31ce2453c82fatagen2026-09-24 13:07 UTC534
        self.port
31ce2453c82fatagen2026-09-24 13:07 UTC535
            .connection_sender()
31ce2453c82fatagen2026-09-24 13:07 UTC536
            .send_replace(ConnectionState::Connecting);
31ce2453c82fatagen2026-09-24 13:07 UTC537
        for event in [
31ce2453c82fatagen2026-09-24 13:07 UTC538
            Event::Authenticated { user_id },
31ce2453c82fatagen2026-09-24 13:07 UTC539
            Event::ConnectionChanged(ConnectionState::Connecting),
31ce2453c82fatagen2026-09-24 13:07 UTC540
        ] {
31ce2453c82fatagen2026-09-24 13:07 UTC541
            if events.send(event).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC542
                self.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC543
                return Err(EngineError::EventReceiverClosed);
31ce2453c82fatagen2026-09-24 13:07 UTC544
            }
31ce2453c82fatagen2026-09-24 13:07 UTC545
        }
31ce2453c82fatagen2026-09-24 13:07 UTC546
        self.start_tasks();
31ce2453c82fatagen2026-09-24 13:07 UTC547
        self.sync.start().await;
31ce2453c82fatagen2026-09-24 13:07 UTC548
31ce2453c82fatagen2026-09-24 13:07 UTC549
        let mut shutdown_reply = None;
673563a76e43atagen2026-09-26 12:30 UTC550
        let mut handles_dropped = false;
673563a76e43atagen2026-09-26 12:30 UTC551
        let mut logouts = self.logouts.subscribe();
fe4133dbcd47atagen2026-09-26 09:05 UTC552
        let ending = loop {
31ce2453c82fatagen2026-09-24 13:07 UTC553
            tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC554
                biased;
fe4133dbcd47atagen2026-09-26 09:05 UTC555
                Some(termination) = self.stops.recv() => break Ok(termination),
673563a76e43atagen2026-09-26 12:30 UTC556
                () = events.closed() => break Err(EngineError::EventReceiverClosed),
673563a76e43atagen2026-09-26 12:30 UTC557
                () = settled(&mut logouts), if handles_dropped => {
673563a76e43atagen2026-09-26 12:30 UTC558
                    break Ok(Termination::HandlesDropped);
673563a76e43atagen2026-09-26 12:30 UTC559
                }
1fe480c16a3catagen2026-09-26 08:44 UTC560
                Some((context, panic)) = self.tasks.join_next(), if !self.tasks.is_empty() => {
1fe480c16a3catagen2026-09-26 08:44 UTC561
                    let Some(message) = panic else { continue };
31ce2453c82fatagen2026-09-24 13:07 UTC562
                    if let TaskContext::Command(label) = context {
1fe480c16a3catagen2026-09-26 08:44 UTC563
                        tracing::error!(label, panic = %message, "command task panicked");
31ce2453c82fatagen2026-09-24 13:07 UTC564
                        continue;
31ce2453c82fatagen2026-09-24 13:07 UTC565
                    }
fe4133dbcd47atagen2026-09-26 09:05 UTC566
                    break Err(EngineError::TaskPanicked { task: context.label() });
31ce2453c82fatagen2026-09-24 13:07 UTC567
                }
5a52012c9b1datagen2026-09-24 23:45 UTC568
                Some(note) = self.notes.recv() => self.actor_note(note).await,
673563a76e43atagen2026-09-26 12:30 UTC569
                request = self.port.recv(), if !handles_dropped => {
673563a76e43atagen2026-09-26 12:30 UTC570
                    // nobody's left to ask, but a logout they asked for still deserves to land
673563a76e43atagen2026-09-26 12:30 UTC571
                    let Some(request) = request else {
673563a76e43atagen2026-09-26 12:30 UTC572
                        handles_dropped = true;
673563a76e43atagen2026-09-26 12:30 UTC573
                        continue;
673563a76e43atagen2026-09-26 12:30 UTC574
                    };
5a52012c9b1datagen2026-09-24 23:45 UTC575
                    if let Some(reply) = self.dispatch(request).await {
5a52012c9b1datagen2026-09-24 23:45 UTC576
                        shutdown_reply = Some(reply);
fe4133dbcd47atagen2026-09-26 09:05 UTC577
                        break Ok(Termination::ShutDown);
5a52012c9b1datagen2026-09-24 23:45 UTC578
                    }
5a52012c9b1datagen2026-09-24 23:45 UTC579
                }
31ce2453c82fatagen2026-09-24 13:07 UTC580
            }
fe4133dbcd47atagen2026-09-26 09:05 UTC581
        };
31ce2453c82fatagen2026-09-24 13:07 UTC582
fe4133dbcd47atagen2026-09-26 09:05 UTC583
        if let Err(error) = &ending {
31ce2453c82fatagen2026-09-24 13:07 UTC584
            let _ = events.send(Event::Failed(error.failure())).await;
31ce2453c82fatagen2026-09-24 13:07 UTC585
        }
31ce2453c82fatagen2026-09-24 13:07 UTC586
        self.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC587
        let _ = events.send(Event::Stopped).await;
31ce2453c82fatagen2026-09-24 13:07 UTC588
        if let Some(reply) = shutdown_reply {
31ce2453c82fatagen2026-09-24 13:07 UTC589
            reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC590
        }
5a52012c9b1datagen2026-09-24 23:45 UTC591
        // everything else still queued answers `Interrupted` as it drops, but a shutdown did happen
5a52012c9b1datagen2026-09-24 23:45 UTC592
        while let Some(Request { command, reply }) = self.port.try_recv() {
5a52012c9b1datagen2026-09-24 23:45 UTC593
            if matches!(command, Command::Lifecycle(LifecycleCommand::Shutdown)) {
5a52012c9b1datagen2026-09-24 23:45 UTC594
                reply.done(Ok(()));
5a52012c9b1datagen2026-09-24 23:45 UTC595
            }
5a52012c9b1datagen2026-09-24 23:45 UTC596
        }
fe4133dbcd47atagen2026-09-26 09:05 UTC597
        ending
31ce2453c82fatagen2026-09-24 13:07 UTC598
    }
31ce2453c82fatagen2026-09-24 13:07 UTC599
31ce2453c82fatagen2026-09-24 13:07 UTC600
    fn start_tasks(&mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC601
        let queues = self.queues.take().expect("engine tasks start once");
cbb715e11963atagen2026-10-24 23:24 UTC602
        if let Some(service) = self.mentions_service.take() {
cbb715e11963atagen2026-10-24 23:24 UTC603
            self.tasks
cbb715e11963atagen2026-10-24 23:24 UTC604
                .spawn(TaskContext::Watcher("mentions service"), service.run());
cbb715e11963atagen2026-10-24 23:24 UTC605
        }
c9effeac326aatagen2026-10-25 10:32 UTC606
        if let Some(service) = self.search_service.take() {
c9effeac326aatagen2026-10-25 10:32 UTC607
            self.tasks
c9effeac326aatagen2026-10-25 10:32 UTC608
                .spawn(TaskContext::Watcher("search service"), service.run());
c9effeac326aatagen2026-10-25 10:32 UTC609
        }
cbb715e11963atagen2026-10-24 23:24 UTC610
        if let Some(updates) = self.mentions_updates.take() {
cbb715e11963atagen2026-10-24 23:24 UTC611
            let events = self.port.event_sender();
cbb715e11963atagen2026-10-24 23:24 UTC612
            self.tasks.spawn(
cbb715e11963atagen2026-10-24 23:24 UTC613
                TaskContext::Watcher("mentions updates"),
cbb715e11963atagen2026-10-24 23:24 UTC614
                crate::mentions::service::watch_updates(updates, events),
cbb715e11963atagen2026-10-24 23:24 UTC615
            );
cbb715e11963atagen2026-10-24 23:24 UTC616
        }
5a52012c9b1datagen2026-09-24 23:45 UTC617
        let (incoming, incoming_verifications) = mpsc::unbounded_channel();
5a52012c9b1datagen2026-09-24 23:45 UTC618
        self.verification_handler = Some(self.verification_handler(incoming));
31ce2453c82fatagen2026-09-24 13:07 UTC619
        let client = &self.client;
31ce2453c82fatagen2026-09-24 13:07 UTC620
        let events = || self.port.event_sender();
f12377bce9b9atagen2026-09-26 12:32 UTC621
        let (notices, notice_queue) = mpsc::unbounded_channel();
743123167e29atagen2026-10-26 04:02 UTC622
        let (enabling_recovery, recovery_enabling) = watch::channel(false);
66db1b3cd895atagen2026-10-25 10:28 UTC623
        let watchers: [(&'static str, EngineBoxFuture<'static, ()>); 15] = [
5a52012c9b1datagen2026-09-24 23:45 UTC624
            (
7c26fad34822atagen2026-09-26 09:41 UTC625
                "upload tracker",
0bfa3f46f0beatagen2026-09-26 09:12 UTC626
                Box::pin(watchers::uploads(
5a52012c9b1datagen2026-09-24 23:45 UTC627
                    client.clone(),
5a52012c9b1datagen2026-09-24 23:45 UTC628
                    client.send_queue().subscribe(),
5a52012c9b1datagen2026-09-24 23:45 UTC629
                    events(),
5a52012c9b1datagen2026-09-24 23:45 UTC630
                )),
5a52012c9b1datagen2026-09-24 23:45 UTC631
            ),
5a52012c9b1datagen2026-09-24 23:45 UTC632
            (
5a52012c9b1datagen2026-09-24 23:45 UTC633
                "room subscription watcher",
5a52012c9b1datagen2026-09-24 23:45 UTC634
                Box::pin(watchers::room_subscriptions(
5a52012c9b1datagen2026-09-24 23:45 UTC635
                    self.sync.clone(),
5a52012c9b1datagen2026-09-24 23:45 UTC636
                    self.subscriptions.subscribe(),
5a52012c9b1datagen2026-09-24 23:45 UTC637
                )),
5a52012c9b1datagen2026-09-24 23:45 UTC638
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC639
            (
31ce2453c82fatagen2026-09-24 13:07 UTC640
                "session change watcher",
f12377bce9b9atagen2026-09-26 12:32 UTC641
                Box::pin(session::session(
f12377bce9b9atagen2026-09-26 12:32 UTC642
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC643
                    queues.session_changes,
f12377bce9b9atagen2026-09-26 12:32 UTC644
                    queues.persistence,
f12377bce9b9atagen2026-09-26 12:32 UTC645
                    notices,
fe4133dbcd47atagen2026-09-26 09:05 UTC646
                    client
fe4133dbcd47atagen2026-09-26 09:05 UTC647
                        .device_id()
fe4133dbcd47atagen2026-09-26 09:05 UTC648
                        .expect("authentication was checked during construction")
fe4133dbcd47atagen2026-09-26 09:05 UTC649
                        .to_owned(),
673563a76e43atagen2026-09-26 12:30 UTC650
                    self.logouts.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC651
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC652
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC653
            (
f12377bce9b9atagen2026-09-26 12:32 UTC654
                "session notice watcher",
f12377bce9b9atagen2026-09-26 12:32 UTC655
                Box::pin(session::session_notices(
f12377bce9b9atagen2026-09-26 12:32 UTC656
                    notice_queue,
f12377bce9b9atagen2026-09-26 12:32 UTC657
                    events(),
f12377bce9b9atagen2026-09-26 12:32 UTC658
                    self.stop.clone(),
f12377bce9b9atagen2026-09-26 12:32 UTC659
                )),
f12377bce9b9atagen2026-09-26 12:32 UTC660
            ),
f12377bce9b9atagen2026-09-26 12:32 UTC661
            (
31ce2453c82fatagen2026-09-24 13:07 UTC662
                "connection state watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC663
                Box::pin(watchers::connection(
31ce2453c82fatagen2026-09-24 13:07 UTC664
                    self.sync.state(),
31ce2453c82fatagen2026-09-24 13:07 UTC665
                    self.sync.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC666
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC667
                    events(),
31ce2453c82fatagen2026-09-24 13:07 UTC668
                    self.port.connection_sender(),
31ce2453c82fatagen2026-09-24 13:07 UTC669
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC670
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC671
            (
31ce2453c82fatagen2026-09-24 13:07 UTC672
                "send queue watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC673
                Box::pin(watchers::send_queue_errors(
31ce2453c82fatagen2026-09-24 13:07 UTC674
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC675
                    client.send_queue().subscribe_errors(),
31ce2453c82fatagen2026-09-24 13:07 UTC676
                    self.port.connection_sender().subscribe(),
31ce2453c82fatagen2026-09-24 13:07 UTC677
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC678
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC679
            (
31ce2453c82fatagen2026-09-24 13:07 UTC680
                "encryption state watcher",
743123167e29atagen2026-10-26 04:02 UTC681
                Box::pin(watchers::encryption(
743123167e29atagen2026-10-26 04:02 UTC682
                    client.clone(),
743123167e29atagen2026-10-26 04:02 UTC683
                    events(),
743123167e29atagen2026-10-26 04:02 UTC684
                    recovery_enabling,
743123167e29atagen2026-10-26 04:02 UTC685
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC686
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC687
            (
31ce2453c82fatagen2026-09-24 13:07 UTC688
                "room list watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC689
                Box::pin(watchers::rooms(client.clone(), events())),
31ce2453c82fatagen2026-09-24 13:07 UTC690
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC691
            (
da8896938cf0atagen2026-10-25 10:04 UTC692
                "event context watcher",
da8896938cf0atagen2026-10-25 10:04 UTC693
                Box::pin(context::close_on_leave(
da8896938cf0atagen2026-10-25 10:04 UTC694
                    client.clone(),
da8896938cf0atagen2026-10-25 10:04 UTC695
                    self.media.clone(),
da8896938cf0atagen2026-10-25 10:04 UTC696
                    self.contexts.clone(),
da8896938cf0atagen2026-10-25 10:04 UTC697
                )),
da8896938cf0atagen2026-10-25 10:04 UTC698
            ),
da8896938cf0atagen2026-10-25 10:04 UTC699
            (
17e042fbc331atagen2026-10-08 12:39 UTC700
                "image pack watcher",
17e042fbc331atagen2026-10-08 12:39 UTC701
                Box::pin(watchers::image_packs(client.clone(), events())),
17e042fbc331atagen2026-10-08 12:39 UTC702
            ),
17e042fbc331atagen2026-10-08 12:39 UTC703
            (
31ce2453c82fatagen2026-09-24 13:07 UTC704
                "notification settings watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC705
                Box::pin(watchers::notification_settings(client.clone(), events())),
31ce2453c82fatagen2026-09-24 13:07 UTC706
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC707
            (
31ce2453c82fatagen2026-09-24 13:07 UTC708
                "room command worker",
31ce2453c82fatagen2026-09-24 13:07 UTC709
                Box::pin(mutations::run_scheduler(
31ce2453c82fatagen2026-09-24 13:07 UTC710
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC711
                    queues.mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC712
                    self.config.command_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC713
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC714
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC715
            (
31ce2453c82fatagen2026-09-24 13:07 UTC716
                "notification command worker",
31ce2453c82fatagen2026-09-24 13:07 UTC717
                Box::pin(commands::run_notifications(
31ce2453c82fatagen2026-09-24 13:07 UTC718
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC719
                    queues.notifications,
31ce2453c82fatagen2026-09-24 13:07 UTC720
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC721
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC722
            (
31ce2453c82fatagen2026-09-24 13:07 UTC723
                "encryption command worker",
31ce2453c82fatagen2026-09-24 13:07 UTC724
                Box::pin(commands::run_encryption(
31ce2453c82fatagen2026-09-24 13:07 UTC725
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC726
                    events(),
31ce2453c82fatagen2026-09-24 13:07 UTC727
                    queues.encryption,
5a52012c9b1datagen2026-09-24 23:45 UTC728
                    incoming_verifications,
743123167e29atagen2026-10-26 04:02 UTC729
                    enabling_recovery,
31ce2453c82fatagen2026-09-24 13:07 UTC730
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC731
            ),
66db1b3cd895atagen2026-10-25 10:28 UTC732
            (
66db1b3cd895atagen2026-10-25 10:28 UTC733
                "thread list worker",
66db1b3cd895atagen2026-10-25 10:28 UTC734
                Box::pin(thread_list::run(
66db1b3cd895atagen2026-10-25 10:28 UTC735
                    client.clone(),
66db1b3cd895atagen2026-10-25 10:28 UTC736
                    events(),
66db1b3cd895atagen2026-10-25 10:28 UTC737
                    self.media.clone(),
66db1b3cd895atagen2026-10-25 10:28 UTC738
                    queues.thread_lists,
66db1b3cd895atagen2026-10-25 10:28 UTC739
                )),
66db1b3cd895atagen2026-10-25 10:28 UTC740
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC741
        ];
31ce2453c82fatagen2026-09-24 13:07 UTC742
        for (label, task) in watchers {
31ce2453c82fatagen2026-09-24 13:07 UTC743
            self.tasks.spawn(TaskContext::Watcher(label), task);
31ce2453c82fatagen2026-09-24 13:07 UTC744
        }
31ce2453c82fatagen2026-09-24 13:07 UTC745
    }
31ce2453c82fatagen2026-09-24 13:07 UTC746
5a52012c9b1datagen2026-09-24 23:45 UTC747
    fn verification_handler(
5a52012c9b1datagen2026-09-24 23:45 UTC748
        &self,
5a52012c9b1datagen2026-09-24 23:45 UTC749
        incoming: IncomingVerifications,
5a52012c9b1datagen2026-09-24 23:45 UTC750
    ) -> matrix_sdk::event_handler::EventHandlerHandle {
5a52012c9b1datagen2026-09-24 23:45 UTC751
        use matrix_sdk::ruma::events::key::verification::request::ToDeviceKeyVerificationRequestEvent;
5a52012c9b1datagen2026-09-24 23:45 UTC752
        self.client.add_event_handler(
5a52012c9b1datagen2026-09-24 23:45 UTC753
            move |event: ToDeviceKeyVerificationRequestEvent, client: Client| {
5a52012c9b1datagen2026-09-24 23:45 UTC754
                let incoming = incoming.clone();
5a52012c9b1datagen2026-09-24 23:45 UTC755
                async move {
5a52012c9b1datagen2026-09-24 23:45 UTC756
                    let request = client
5a52012c9b1datagen2026-09-24 23:45 UTC757
                        .encryption()
5a52012c9b1datagen2026-09-24 23:45 UTC758
                        .get_verification_request(
5a52012c9b1datagen2026-09-24 23:45 UTC759
                            &event.sender,
5a52012c9b1datagen2026-09-24 23:45 UTC760
                            event.content.transaction_id.as_str(),
5a52012c9b1datagen2026-09-24 23:45 UTC761
                        )
5a52012c9b1datagen2026-09-24 23:45 UTC762
                        .await;
5a52012c9b1datagen2026-09-24 23:45 UTC763
                    if let Some(request) = request {
5a52012c9b1datagen2026-09-24 23:45 UTC764
                        let _ = incoming.send(request);
5a52012c9b1datagen2026-09-24 23:45 UTC765
                    }
5a52012c9b1datagen2026-09-24 23:45 UTC766
                }
5a52012c9b1datagen2026-09-24 23:45 UTC767
            },
5a52012c9b1datagen2026-09-24 23:45 UTC768
        )
5a52012c9b1datagen2026-09-24 23:45 UTC769
    }
5a52012c9b1datagen2026-09-24 23:45 UTC770
31ce2453c82fatagen2026-09-24 13:07 UTC771
    /// hands back the shutdown request's responder, which is answered once we've stopped
31ce2453c82fatagen2026-09-24 13:07 UTC772
    async fn dispatch(&mut self, Request { command, reply }: Request) -> Option<Responder> {
2819830aef32atagen2026-10-26 08:42 UTC773
        tracing::debug!(command = %executor::label(&command), "engine command");
31ce2453c82fatagen2026-09-24 13:07 UTC774
        let client = self.client.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC775
        match command {
31ce2453c82fatagen2026-09-24 13:07 UTC776
            Command::Lifecycle(LifecycleCommand::Shutdown) => return Some(reply),
2006a11ce142atagen2026-10-26 04:02 UTC777
            Command::Account(AccountCommand::Logout { remove_pushers }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC778
                let events = self.port.event_sender();
31ce2453c82fatagen2026-09-24 13:07 UTC779
                let stop = self.stop.clone();
673563a76e43atagen2026-09-26 12:30 UTC780
                let logouts = self.logouts.clone();
673563a76e43atagen2026-09-26 12:30 UTC781
                logouts.send_modify(|logouts| logouts.in_flight += 1);
31ce2453c82fatagen2026-09-24 13:07 UTC782
                self.spawn_command("logout", reply, async move {
2006a11ce142atagen2026-10-26 04:02 UTC783
                    for pusher in remove_pushers {
2006a11ce142atagen2026-10-26 04:02 UTC784
                        if let Err(error) = crate::transport::remove_pusher(&client, pusher).await {
2006a11ce142atagen2026-10-26 04:02 UTC785
                            tracing::debug!(%error, "removing a pusher before logout failed");
2006a11ce142atagen2026-10-26 04:02 UTC786
                        }
2006a11ce142atagen2026-10-26 04:02 UTC787
                    }
673563a76e43atagen2026-09-26 12:30 UTC788
                    let result = client.logout().await;
673563a76e43atagen2026-09-26 12:30 UTC789
                    if result.is_ok() {
673563a76e43atagen2026-09-26 12:30 UTC790
                        let _ = events.send(Event::LoggedOut).await;
673563a76e43atagen2026-09-26 12:30 UTC791
                        let _ = stop.send(Termination::LoggedOut);
673563a76e43atagen2026-09-26 12:30 UTC792
                    }
673563a76e43atagen2026-09-26 12:30 UTC793
                    let mut rejected = None;
673563a76e43atagen2026-09-26 12:30 UTC794
                    logouts.send_if_modified(|logouts| {
673563a76e43atagen2026-09-26 12:30 UTC795
                        if result.is_err() && logouts.in_flight == 1 {
673563a76e43atagen2026-09-26 12:30 UTC796
                            rejected = logouts.rejected.take();
673563a76e43atagen2026-09-26 12:30 UTC797
                        }
673563a76e43atagen2026-09-26 12:30 UTC798
                        false
673563a76e43atagen2026-09-26 12:30 UTC799
                    });
673563a76e43atagen2026-09-26 12:30 UTC800
                    if let Some(soft_logout) = rejected {
f12377bce9b9atagen2026-09-26 12:32 UTC801
                        session::reject_session(&events, &stop, soft_logout).await;
673563a76e43atagen2026-09-26 12:30 UTC802
                    }
673563a76e43atagen2026-09-26 12:30 UTC803
                    logouts.send_modify(|logouts| logouts.in_flight -= 1);
673563a76e43atagen2026-09-26 12:30 UTC804
                    result.map_err(|source| Error::from_sdk("logout failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC805
                });
31ce2453c82fatagen2026-09-24 13:07 UTC806
            }
31ce2453c82fatagen2026-09-24 13:07 UTC807
            Command::Account(AccountCommand::SetAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC808
                event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC809
                content,
31ce2453c82fatagen2026-09-24 13:07 UTC810
            }) => enqueue(
31ce2453c82fatagen2026-09-24 13:07 UTC811
                &self.mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC812
                Job {
31ce2453c82fatagen2026-09-24 13:07 UTC813
                    command: Mutation::GlobalAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC814
                        event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC815
                        content,
31ce2453c82fatagen2026-09-24 13:07 UTC816
                    },
31ce2453c82fatagen2026-09-24 13:07 UTC817
                    reply,
31ce2453c82fatagen2026-09-24 13:07 UTC818
                },
31ce2453c82fatagen2026-09-24 13:07 UTC819
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC820
            Command::Account(AccountCommand::SetDisplayName(name)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC821
                self.spawn_command("set display name", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC822
                    client
31ce2453c82fatagen2026-09-24 13:07 UTC823
                        .account()
31ce2453c82fatagen2026-09-24 13:07 UTC824
                        .set_display_name(name.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC825
                        .await
31ce2453c82fatagen2026-09-24 13:07 UTC826
                        .map_err(|source| Error::from_sdk("setting display name failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC827
                });
31ce2453c82fatagen2026-09-24 13:07 UTC828
            }
31ce2453c82fatagen2026-09-24 13:07 UTC829
            Command::Account(AccountCommand::SetAvatar(avatar)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC830
                self.spawn_command("set avatar", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC831
                    client
31ce2453c82fatagen2026-09-24 13:07 UTC832
                        .account()
31ce2453c82fatagen2026-09-24 13:07 UTC833
                        .set_avatar_url(avatar.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC834
                        .await
31ce2453c82fatagen2026-09-24 13:07 UTC835
                        .map_err(|source| Error::from_sdk("setting avatar failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC836
                });
31ce2453c82fatagen2026-09-24 13:07 UTC837
            }
31ce2453c82fatagen2026-09-24 13:07 UTC838
            Command::Account(AccountCommand::UploadAvatar { mimetype, bytes }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC839
                let max_bytes = self.config.max_media_bytes;
31ce2453c82fatagen2026-09-24 13:07 UTC840
                self.spawn_command("upload avatar", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC841
                    commands::upload_avatar(&client, &mimetype, bytes, max_bytes).await
31ce2453c82fatagen2026-09-24 13:07 UTC842
                });
31ce2453c82fatagen2026-09-24 13:07 UTC843
            }
9bd34e25bb86atagen2026-10-24 07:23 UTC844
            Command::Account(AccountCommand::UploadMedia { mimetype, bytes }) => {
9bd34e25bb86atagen2026-10-24 07:23 UTC845
                let max_bytes = self.config.max_media_bytes;
9bd34e25bb86atagen2026-10-24 07:23 UTC846
                self.tasks
9bd34e25bb86atagen2026-10-24 07:23 UTC847
                    .spawn(TaskContext::Command("upload media"), async move {
9bd34e25bb86atagen2026-10-24 07:23 UTC848
                        let uploaded =
9bd34e25bb86atagen2026-10-24 07:23 UTC849
                            commands::upload_media(&client, &mimetype, bytes, max_bytes).await;
9bd34e25bb86atagen2026-10-24 07:23 UTC850
                        reply.send(uploaded.map(CommandOutput::Mxc));
9bd34e25bb86atagen2026-10-24 07:23 UTC851
                    });
9bd34e25bb86atagen2026-10-24 07:23 UTC852
            }
9bd34e25bb86atagen2026-10-24 07:23 UTC853
            Command::Account(AccountCommand::IgnoreUser(user_id)) => enqueue(
9bd34e25bb86atagen2026-10-24 07:23 UTC854
                &self.mutations,
9bd34e25bb86atagen2026-10-24 07:23 UTC855
                Job {
9bd34e25bb86atagen2026-10-24 07:23 UTC856
                    command: Mutation::Ignore {
9bd34e25bb86atagen2026-10-24 07:23 UTC857
                        user_id,
9bd34e25bb86atagen2026-10-24 07:23 UTC858
                        ignored: true,
9bd34e25bb86atagen2026-10-24 07:23 UTC859
                    },
9bd34e25bb86atagen2026-10-24 07:23 UTC860
                    reply,
9bd34e25bb86atagen2026-10-24 07:23 UTC861
                },
9bd34e25bb86atagen2026-10-24 07:23 UTC862
            ),
9bd34e25bb86atagen2026-10-24 07:23 UTC863
            Command::Account(AccountCommand::UnignoreUser(user_id)) => enqueue(
9bd34e25bb86atagen2026-10-24 07:23 UTC864
                &self.mutations,
9bd34e25bb86atagen2026-10-24 07:23 UTC865
                Job {
9bd34e25bb86atagen2026-10-24 07:23 UTC866
                    command: Mutation::Ignore {
9bd34e25bb86atagen2026-10-24 07:23 UTC867
                        user_id,
9bd34e25bb86atagen2026-10-24 07:23 UTC868
                        ignored: false,
9bd34e25bb86atagen2026-10-24 07:23 UTC869
                    },
9bd34e25bb86atagen2026-10-24 07:23 UTC870
                    reply,
9bd34e25bb86atagen2026-10-24 07:23 UTC871
                },
9bd34e25bb86atagen2026-10-24 07:23 UTC872
            ),
9bd34e25bb86atagen2026-10-24 07:23 UTC873
            Command::Account(AccountCommand::ReorderSpaces(order)) => enqueue(
9bd34e25bb86atagen2026-10-24 07:23 UTC874
                &self.mutations,
9bd34e25bb86atagen2026-10-24 07:23 UTC875
                Job {
9bd34e25bb86atagen2026-10-24 07:23 UTC876
                    command: Mutation::ReorderSpaces(order),
9bd34e25bb86atagen2026-10-24 07:23 UTC877
                    reply,
9bd34e25bb86atagen2026-10-24 07:23 UTC878
                },
9bd34e25bb86atagen2026-10-24 07:23 UTC879
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC880
            Command::Account(AccountCommand::RenameDevice {
31ce2453c82fatagen2026-09-24 13:07 UTC881
                device_id,
31ce2453c82fatagen2026-09-24 13:07 UTC882
                display_name,
31ce2453c82fatagen2026-09-24 13:07 UTC883
            }) => self.spawn_command("rename device", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC884
                commands::rename_device(&client, &device_id, &display_name).await
31ce2453c82fatagen2026-09-24 13:07 UTC885
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC886
            Command::Account(AccountCommand::DeleteDevices { device_ids, auth }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC887
                self.spawn_command("delete devices", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC888
                    commands::delete_devices(&client, &device_ids, auth).await
31ce2453c82fatagen2026-09-24 13:07 UTC889
                });
31ce2453c82fatagen2026-09-24 13:07 UTC890
            }
38c4caecdb3datagen2026-09-26 13:35 UTC891
            Command::Room(RoomCommand::Open(room_id)) => {
38c4caecdb3datagen2026-09-26 13:35 UTC892
                if self.config.prepare_to_send {
38c4caecdb3datagen2026-09-26 13:35 UTC893
                    self.prepare_quietly(room_id.clone());
38c4caecdb3datagen2026-09-26 13:35 UTC894
                }
38c4caecdb3datagen2026-09-26 13:35 UTC895
                self.open_room(room_id, reply).await;
38c4caecdb3datagen2026-09-26 13:35 UTC896
            }
31ce2453c82fatagen2026-09-24 13:07 UTC897
            Command::Room(RoomCommand::Close(room_id)) => self.close_room(room_id, reply).await,
38c4caecdb3datagen2026-09-26 13:35 UTC898
            Command::Room(RoomCommand::PrepareToSend(room_id)) => {
38c4caecdb3datagen2026-09-26 13:35 UTC899
                let flight = self.preparations.join(&client, room_id);
38c4caecdb3datagen2026-09-26 13:35 UTC900
                self.spawn_command("prepare to send", reply, flight);
38c4caecdb3datagen2026-09-26 13:35 UTC901
            }
31ce2453c82fatagen2026-09-24 13:07 UTC902
            Command::Room(RoomCommand::SetTyping { room_id, typing }) => {
38c4caecdb3datagen2026-09-26 13:35 UTC903
                if typing && self.config.prepare_to_send {
38c4caecdb3datagen2026-09-26 13:35 UTC904
                    self.prepare_quietly(room_id.clone());
38c4caecdb3datagen2026-09-26 13:35 UTC905
                }
31ce2453c82fatagen2026-09-24 13:07 UTC906
                self.spawn_command("typing", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC907
                    commands::set_typing(&client, &room_id, typing).await
31ce2453c82fatagen2026-09-24 13:07 UTC908
                });
31ce2453c82fatagen2026-09-24 13:07 UTC909
            }
31ce2453c82fatagen2026-09-24 13:07 UTC910
            Command::Room(RoomCommand::CancelSend {
31ce2453c82fatagen2026-09-24 13:07 UTC911
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC912
                transaction_id,
31ce2453c82fatagen2026-09-24 13:07 UTC913
            }) => self.spawn_command("cancel send", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC914
                commands::cancel_send(&client, &room_id, &transaction_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC915
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC916
            Command::Room(RoomCommand::RetrySend {
31ce2453c82fatagen2026-09-24 13:07 UTC917
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC918
                transaction_id,
31ce2453c82fatagen2026-09-24 13:07 UTC919
            }) => self.spawn_command("retry send", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC920
                commands::retry_send(&client, &room_id, &transaction_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC921
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC922
            Command::Room(command) => enqueue(
31ce2453c82fatagen2026-09-24 13:07 UTC923
                &self.mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC924
                Job {
31ce2453c82fatagen2026-09-24 13:07 UTC925
                    command: Mutation::Room(command),
31ce2453c82fatagen2026-09-24 13:07 UTC926
                    reply,
31ce2453c82fatagen2026-09-24 13:07 UTC927
                },
31ce2453c82fatagen2026-09-24 13:07 UTC928
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC929
            Command::Timeline(command) => self.route_timeline(command, reply),
89bc9f8d246datagen2026-10-24 13:40 UTC930
            Command::EventContext(command) => {
da8896938cf0atagen2026-10-25 10:04 UTC931
                let (client, media, contexts) =
da8896938cf0atagen2026-10-25 10:04 UTC932
                    (client.clone(), self.media.clone(), self.contexts.clone());
cbb715e11963atagen2026-10-24 23:24 UTC933
                self.tasks
cbb715e11963atagen2026-10-24 23:24 UTC934
                    .spawn(TaskContext::Command("event context"), async move {
da8896938cf0atagen2026-10-25 10:04 UTC935
                        reply.send(context::execute(client, media, contexts, command).await);
cbb715e11963atagen2026-10-24 23:24 UTC936
                    });
cbb715e11963atagen2026-10-24 23:24 UTC937
            }
cbb715e11963atagen2026-10-24 23:24 UTC938
            Command::Mentions(command) => {
cbb715e11963atagen2026-10-24 23:24 UTC939
                let mentions = self.mentions.clone();
cbb715e11963atagen2026-10-24 23:24 UTC940
                self.tasks
cbb715e11963atagen2026-10-24 23:24 UTC941
                    .spawn(TaskContext::Command("mentions command"), async move {
cbb715e11963atagen2026-10-24 23:24 UTC942
                        reply.send(mentions.execute(command).await);
cbb715e11963atagen2026-10-24 23:24 UTC943
                    });
89bc9f8d246datagen2026-10-24 13:40 UTC944
            }
c9effeac326aatagen2026-10-25 10:32 UTC945
            Command::Search(command) => {
c9effeac326aatagen2026-10-25 10:32 UTC946
                let search = self.search.clone();
c9effeac326aatagen2026-10-25 10:32 UTC947
                self.tasks
c9effeac326aatagen2026-10-25 10:32 UTC948
                    .spawn(TaskContext::Command("search command"), async move {
c9effeac326aatagen2026-10-25 10:32 UTC949
                        reply.send(search.execute(command).await);
c9effeac326aatagen2026-10-25 10:32 UTC950
                    });
c9effeac326aatagen2026-10-25 10:32 UTC951
            }
80381e06a573atagen2026-10-25 03:02 UTC952
            Command::HistoryRepair(command) => self.route_repair(command, reply),
31ce2453c82fatagen2026-09-24 13:07 UTC953
            Command::Media(MediaCommand::Upload(upload)) => self.upload(upload, reply),
31ce2453c82fatagen2026-09-24 13:07 UTC954
            Command::Encryption(command) => enqueue(&self.encryption, Job { command, reply }),
31ce2453c82fatagen2026-09-24 13:07 UTC955
            Command::Notification(command) => {
31ce2453c82fatagen2026-09-24 13:07 UTC956
                enqueue(&self.notifications, Job { command, reply });
31ce2453c82fatagen2026-09-24 13:07 UTC957
            }
fa51490b6ae9atagen2026-10-24 08:41 UTC958
            Command::Pack(command) => self.pack(command, reply),
66db1b3cd895atagen2026-10-25 10:28 UTC959
            Command::ThreadList(command) => enqueue(&self.thread_lists, Job { command, reply }),
31ce2453c82fatagen2026-09-24 13:07 UTC960
        }
31ce2453c82fatagen2026-09-24 13:07 UTC961
        None
31ce2453c82fatagen2026-09-24 13:07 UTC962
    }
31ce2453c82fatagen2026-09-24 13:07 UTC963
31ce2453c82fatagen2026-09-24 13:07 UTC964
    fn spawn_command(
31ce2453c82fatagen2026-09-24 13:07 UTC965
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC966
        label: &'static str,
31ce2453c82fatagen2026-09-24 13:07 UTC967
        reply: Responder,
1fe480c16a3catagen2026-09-26 08:44 UTC968
        work: impl Future<Output = Result<(), Error>> + SendOutsideWasm + 'static,
31ce2453c82fatagen2026-09-24 13:07 UTC969
    ) {
31ce2453c82fatagen2026-09-24 13:07 UTC970
        self.tasks.spawn(TaskContext::Command(label), async move {
31ce2453c82fatagen2026-09-24 13:07 UTC971
            reply.done(work.await);
31ce2453c82fatagen2026-09-24 13:07 UTC972
        });
31ce2453c82fatagen2026-09-24 13:07 UTC973
    }
31ce2453c82fatagen2026-09-24 13:07 UTC974
38c4caecdb3datagen2026-09-26 13:35 UTC975
    fn prepare_quietly(&mut self, room_id: OwnedRoomId) {
38c4caecdb3datagen2026-09-26 13:35 UTC976
        let flight = self.preparations.join(&self.client, room_id);
38c4caecdb3datagen2026-09-26 13:35 UTC977
        self.tasks
38c4caecdb3datagen2026-09-26 13:35 UTC978
            .spawn(TaskContext::Command("prepare to send"), async move {
38c4caecdb3datagen2026-09-26 13:35 UTC979
                let _ = flight.await;
38c4caecdb3datagen2026-09-26 13:35 UTC980
            });
38c4caecdb3datagen2026-09-26 13:35 UTC981
    }
38c4caecdb3datagen2026-09-26 13:35 UTC982
fa51490b6ae9atagen2026-10-24 08:41 UTC983
    fn pack(&mut self, command: PackCommand, reply: Responder) {
fa51490b6ae9atagen2026-10-24 08:41 UTC984
        let edit = |pack, edit| Mutation::Pack { pack, edit };
fa51490b6ae9atagen2026-10-24 08:41 UTC985
        let mutation = match command {
fa51490b6ae9atagen2026-10-24 08:41 UTC986
            PackCommand::CreateRoomPack {
fa51490b6ae9atagen2026-10-24 08:41 UTC987
                room_id,
fa51490b6ae9atagen2026-10-24 08:41 UTC988
                state_key,
fa51490b6ae9atagen2026-10-24 08:41 UTC989
                display_name,
fa51490b6ae9atagen2026-10-24 08:41 UTC990
            } => edit(
fa51490b6ae9atagen2026-10-24 08:41 UTC991
                ImagePackSource::Room { room_id, state_key },
fa51490b6ae9atagen2026-10-24 08:41 UTC992
                PackEdit::Create { display_name },
fa51490b6ae9atagen2026-10-24 08:41 UTC993
            ),
fa51490b6ae9atagen2026-10-24 08:41 UTC994
            PackCommand::Delete(pack) => edit(pack, PackEdit::Delete),
fa51490b6ae9atagen2026-10-24 08:41 UTC995
            PackCommand::SetMeta {
fa51490b6ae9atagen2026-10-24 08:41 UTC996
                pack,
fa51490b6ae9atagen2026-10-24 08:41 UTC997
                display_name,
fa51490b6ae9atagen2026-10-24 08:41 UTC998
                avatar,
fa51490b6ae9atagen2026-10-24 08:41 UTC999
                usage,
fa51490b6ae9atagen2026-10-24 08:41 UTC1000
            } => edit(
fa51490b6ae9atagen2026-10-24 08:41 UTC1001
                pack,
fa51490b6ae9atagen2026-10-24 08:41 UTC1002
                PackEdit::Meta {
fa51490b6ae9atagen2026-10-24 08:41 UTC1003
                    display_name,
fa51490b6ae9atagen2026-10-24 08:41 UTC1004
                    avatar,
fa51490b6ae9atagen2026-10-24 08:41 UTC1005
                    usage,
fa51490b6ae9atagen2026-10-24 08:41 UTC1006
                },
fa51490b6ae9atagen2026-10-24 08:41 UTC1007
            ),
fa51490b6ae9atagen2026-10-24 08:41 UTC1008
            PackCommand::PutImage {
fa51490b6ae9atagen2026-10-24 08:41 UTC1009
                pack,
fa51490b6ae9atagen2026-10-24 08:41 UTC1010
                shortcode,
fa51490b6ae9atagen2026-10-24 08:41 UTC1011
                mxc,
fa51490b6ae9atagen2026-10-24 08:41 UTC1012
                body,
fa51490b6ae9atagen2026-10-24 08:41 UTC1013
                usage,
fa51490b6ae9atagen2026-10-24 08:41 UTC1014
                info,
fa51490b6ae9atagen2026-10-24 08:41 UTC1015
            } => edit(
fa51490b6ae9atagen2026-10-24 08:41 UTC1016
                pack,
fa51490b6ae9atagen2026-10-24 08:41 UTC1017
                PackEdit::Put {
fa51490b6ae9atagen2026-10-24 08:41 UTC1018
                    shortcode,
fa51490b6ae9atagen2026-10-24 08:41 UTC1019
                    mxc,
fa51490b6ae9atagen2026-10-24 08:41 UTC1020
                    body,
fa51490b6ae9atagen2026-10-24 08:41 UTC1021
                    usage,
fa51490b6ae9atagen2026-10-24 08:41 UTC1022
                    info,
fa51490b6ae9atagen2026-10-24 08:41 UTC1023
                },
fa51490b6ae9atagen2026-10-24 08:41 UTC1024
            ),
fa51490b6ae9atagen2026-10-24 08:41 UTC1025
            PackCommand::RenameImage { pack, from, to } => {
fa51490b6ae9atagen2026-10-24 08:41 UTC1026
                edit(pack, PackEdit::Rename { from, to })
fa51490b6ae9atagen2026-10-24 08:41 UTC1027
            }
fa51490b6ae9atagen2026-10-24 08:41 UTC1028
            PackCommand::RemoveImage { pack, shortcode } => {
fa51490b6ae9atagen2026-10-24 08:41 UTC1029
                edit(pack, PackEdit::Remove { shortcode })
fa51490b6ae9atagen2026-10-24 08:41 UTC1030
            }
fa51490b6ae9atagen2026-10-24 08:41 UTC1031
            PackCommand::SetEnabledEverywhere {
fa51490b6ae9atagen2026-10-24 08:41 UTC1032
                room_id,
fa51490b6ae9atagen2026-10-24 08:41 UTC1033
                state_key,
fa51490b6ae9atagen2026-10-24 08:41 UTC1034
                enabled,
fa51490b6ae9atagen2026-10-24 08:41 UTC1035
            } => Mutation::PackEnabledEverywhere {
fa51490b6ae9atagen2026-10-24 08:41 UTC1036
                room_id,
fa51490b6ae9atagen2026-10-24 08:41 UTC1037
                state_key,
fa51490b6ae9atagen2026-10-24 08:41 UTC1038
                enabled,
fa51490b6ae9atagen2026-10-24 08:41 UTC1039
            },
fa51490b6ae9atagen2026-10-24 08:41 UTC1040
            command => {
fa51490b6ae9atagen2026-10-24 08:41 UTC1041
                let client = self.client.clone();
fa51490b6ae9atagen2026-10-24 08:41 UTC1042
                let mutations = self.mutations.clone();
fa51490b6ae9atagen2026-10-24 08:41 UTC1043
                let max_bytes = self.config.max_media_bytes;
fa51490b6ae9atagen2026-10-24 08:41 UTC1044
                self.tasks.spawn(TaskContext::Command("pack"), async move {
fa51490b6ae9atagen2026-10-24 08:41 UTC1045
                    reply.send(packs::run_detached(client, mutations, max_bytes, command).await);
fa51490b6ae9atagen2026-10-24 08:41 UTC1046
                });
fa51490b6ae9atagen2026-10-24 08:41 UTC1047
                return;
fa51490b6ae9atagen2026-10-24 08:41 UTC1048
            }
fa51490b6ae9atagen2026-10-24 08:41 UTC1049
        };
fa51490b6ae9atagen2026-10-24 08:41 UTC1050
        enqueue(
fa51490b6ae9atagen2026-10-24 08:41 UTC1051
            &self.mutations,
fa51490b6ae9atagen2026-10-24 08:41 UTC1052
            Job {
fa51490b6ae9atagen2026-10-24 08:41 UTC1053
                command: mutation,
fa51490b6ae9atagen2026-10-24 08:41 UTC1054
                reply,
fa51490b6ae9atagen2026-10-24 08:41 UTC1055
            },
fa51490b6ae9atagen2026-10-24 08:41 UTC1056
        );
fa51490b6ae9atagen2026-10-24 08:41 UTC1057
    }
fa51490b6ae9atagen2026-10-24 08:41 UTC1058
31ce2453c82fatagen2026-09-24 13:07 UTC1059
    fn upload(&mut self, upload: crate::AttachmentUpload, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC1060
        let Some(room) = self.client.get_room(upload.timeline_id.room_id()) else {
31ce2453c82fatagen2026-09-24 13:07 UTC1061
            return reply.done(Err(Error::room_unknown()));
31ce2453c82fatagen2026-09-24 13:07 UTC1062
        };
31ce2453c82fatagen2026-09-24 13:07 UTC1063
        let max_bytes = self.config.max_media_bytes;
31ce2453c82fatagen2026-09-24 13:07 UTC1064
        self.spawn_command("upload", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC1065
            media::queue_upload(room, upload, max_bytes).await
31ce2453c82fatagen2026-09-24 13:07 UTC1066
        });
31ce2453c82fatagen2026-09-24 13:07 UTC1067
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1068
31ce2453c82fatagen2026-09-24 13:07 UTC1069
    fn route_timeline(&mut self, command: TimelineCommand, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC1070
        let sender = match self.rooms.get(command.room_id()) {
31ce2453c82fatagen2026-09-24 13:07 UTC1071
            Some(RoomSlot::Active { sender, .. }) => Some(sender.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC1072
            _ => None,
31ce2453c82fatagen2026-09-24 13:07 UTC1073
        };
31ce2453c82fatagen2026-09-24 13:07 UTC1074
        match (sender, command) {
31ce2453c82fatagen2026-09-24 13:07 UTC1075
            (Some(sender), command) => send_actor(&sender, ActorRequest::Execute(command, reply)),
31ce2453c82fatagen2026-09-24 13:07 UTC1076
            (
31ce2453c82fatagen2026-09-24 13:07 UTC1077
                None,
31ce2453c82fatagen2026-09-24 13:07 UTC1078
                TimelineCommand::MarkRead {
31ce2453c82fatagen2026-09-24 13:07 UTC1079
                    timeline_id,
31ce2453c82fatagen2026-09-24 13:07 UTC1080
                    event_id,
31ce2453c82fatagen2026-09-24 13:07 UTC1081
                },
31ce2453c82fatagen2026-09-24 13:07 UTC1082
            ) => {
31ce2453c82fatagen2026-09-24 13:07 UTC1083
                let client = self.client.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC1084
                self.spawn_command("mark read", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC1085
                    commands::mark_read(&client, &timeline_id, event_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC1086
                });
31ce2453c82fatagen2026-09-24 13:07 UTC1087
            }
31ce2453c82fatagen2026-09-24 13:07 UTC1088
            (None, _) => reply.done(Err(Error::new(ErrorKind::RoomNotOpen, "room is not open"))),
31ce2453c82fatagen2026-09-24 13:07 UTC1089
        }
31ce2453c82fatagen2026-09-24 13:07 UTC1090
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1091
80381e06a573atagen2026-10-25 03:02 UTC1092
    fn route_repair(&mut self, command: crate::HistoryRepairCommand, reply: Responder) {
80381e06a573atagen2026-10-25 03:02 UTC1093
        use crate::HistoryRepairCommand;
80381e06a573atagen2026-10-25 03:02 UTC1094
        let room_id = match &command {
d9b96952ec60atagen2026-10-25 09:07 UTC1095
            HistoryRepairCommand::Cancel { room_id }
d9b96952ec60atagen2026-10-25 09:07 UTC1096
            | HistoryRepairCommand::RetryRecent { room_id }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC1097
            | HistoryRepairCommand::FillGap { room_id, .. } => room_id,
80381e06a573atagen2026-10-25 03:02 UTC1098
        };
80381e06a573atagen2026-10-25 03:02 UTC1099
        match self.rooms.get(room_id) {
80381e06a573atagen2026-10-25 03:02 UTC1100
            Some(RoomSlot::Active { sender, .. }) => {
80381e06a573atagen2026-10-25 03:02 UTC1101
                send_actor(sender, ActorRequest::Repair(command, reply))
80381e06a573atagen2026-10-25 03:02 UTC1102
            }
80381e06a573atagen2026-10-25 03:02 UTC1103
            Some(RoomSlot::Closing { .. }) => {
80381e06a573atagen2026-10-25 03:02 UTC1104
                reply.done(Err(Error::new(ErrorKind::RoomBusy, "room is closing")))
80381e06a573atagen2026-10-25 03:02 UTC1105
            }
80381e06a573atagen2026-10-25 03:02 UTC1106
            None => reply.done(Err(Error::new(ErrorKind::RoomNotOpen, "room is not open"))),
80381e06a573atagen2026-10-25 03:02 UTC1107
        }
80381e06a573atagen2026-10-25 03:02 UTC1108
    }
80381e06a573atagen2026-10-25 03:02 UTC1109
31ce2453c82fatagen2026-09-24 13:07 UTC1110
    async fn open_room(&mut self, room_id: OwnedRoomId, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC1111
        match self.rooms.get_mut(&room_id) {
31ce2453c82fatagen2026-09-24 13:07 UTC1112
            Some(RoomSlot::Active { sender, .. }) => send_actor(sender, ActorRequest::Open(reply)),
31ce2453c82fatagen2026-09-24 13:07 UTC1113
            Some(RoomSlot::Closing { pending, .. }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC1114
                defer(pending, Deferred::Open(reply), self.config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC1115
            }
31ce2453c82fatagen2026-09-24 13:07 UTC1116
            None => self.start_actor(room_id, reply).await,
31ce2453c82fatagen2026-09-24 13:07 UTC1117
        }
31ce2453c82fatagen2026-09-24 13:07 UTC1118
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1119
31ce2453c82fatagen2026-09-24 13:07 UTC1120
    async fn close_room(&mut self, room_id: OwnedRoomId, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC1121
        let (generation, sender) = match self.rooms.get_mut(&room_id) {
31ce2453c82fatagen2026-09-24 13:07 UTC1122
            None => return reply.done(Ok(())),
31ce2453c82fatagen2026-09-24 13:07 UTC1123
            Some(RoomSlot::Closing { pending, .. }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC1124
                return defer(
31ce2453c82fatagen2026-09-24 13:07 UTC1125
                    pending,
31ce2453c82fatagen2026-09-24 13:07 UTC1126
                    Deferred::Close(reply),
31ce2453c82fatagen2026-09-24 13:07 UTC1127
                    self.config.command_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC1128
                );
31ce2453c82fatagen2026-09-24 13:07 UTC1129
            }
31ce2453c82fatagen2026-09-24 13:07 UTC1130
            Some(RoomSlot::Active { generation, sender }) => (*generation, sender.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC1131
        };
31ce2453c82fatagen2026-09-24 13:07 UTC1132
        match sender.try_send(ActorRequest::Close(reply)) {
5a52012c9b1datagen2026-09-24 23:45 UTC1133
            Ok(()) => {}
5a52012c9b1datagen2026-09-24 23:45 UTC1134
            // the actor already left on its own, which is closed enough
5a52012c9b1datagen2026-09-24 23:45 UTC1135
            Err(mpsc::error::TrySendError::Closed(request)) => request.into_reply().done(Ok(())),
31ce2453c82fatagen2026-09-24 13:07 UTC1136
            Err(mpsc::error::TrySendError::Full(request)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC1137
                return request.into_reply().done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC1138
                    ErrorKind::RoomBusy,
31ce2453c82fatagen2026-09-24 13:07 UTC1139
                    "room command queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC1140
                )));
31ce2453c82fatagen2026-09-24 13:07 UTC1141
            }
31ce2453c82fatagen2026-09-24 13:07 UTC1142
        }
31ce2453c82fatagen2026-09-24 13:07 UTC1143
        self.rooms.insert(
31ce2453c82fatagen2026-09-24 13:07 UTC1144
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC1145
            RoomSlot::Closing {
31ce2453c82fatagen2026-09-24 13:07 UTC1146
                generation,
31ce2453c82fatagen2026-09-24 13:07 UTC1147
                pending: VecDeque::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC1148
            },
31ce2453c82fatagen2026-09-24 13:07 UTC1149
        );
5a52012c9b1datagen2026-09-24 23:45 UTC1150
        self.update_room_subscriptions();
31ce2453c82fatagen2026-09-24 13:07 UTC1151
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1152
31ce2453c82fatagen2026-09-24 13:07 UTC1153
    async fn start_actor(&mut self, room_id: OwnedRoomId, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC1154
        let Some(room) = self.client.get_room(&room_id) else {
31ce2453c82fatagen2026-09-24 13:07 UTC1155
            return reply.done(Err(Error::room_unknown()));
31ce2453c82fatagen2026-09-24 13:07 UTC1156
        };
31ce2453c82fatagen2026-09-24 13:07 UTC1157
        self.next_generation = self.next_generation.wrapping_add(1);
31ce2453c82fatagen2026-09-24 13:07 UTC1158
        let generation = self.next_generation;
31ce2453c82fatagen2026-09-24 13:07 UTC1159
        let (sender, requests) = mpsc::channel(self.config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC1160
        let (supervisor, panics) = Supervisor::new();
31ce2453c82fatagen2026-09-24 13:07 UTC1161
        let actor = RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC1162
            room_id: room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC1163
            generation,
31ce2453c82fatagen2026-09-24 13:07 UTC1164
            room,
31ce2453c82fatagen2026-09-24 13:07 UTC1165
            requests,
5a52012c9b1datagen2026-09-24 23:45 UTC1166
            notes: self.note_sender.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC1167
            context: SessionContext {
31ce2453c82fatagen2026-09-24 13:07 UTC1168
                events: self.port.event_sender(),
31ce2453c82fatagen2026-09-24 13:07 UTC1169
                media: self.media.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC1170
                current_user_id: self.client.user_id().map(ToOwned::to_owned),
31ce2453c82fatagen2026-09-24 13:07 UTC1171
                initial_history_limit: self.config.initial_history_limit,
31ce2453c82fatagen2026-09-24 13:07 UTC1172
                supervisor,
31ce2453c82fatagen2026-09-24 13:07 UTC1173
            },
31ce2453c82fatagen2026-09-24 13:07 UTC1174
            panics,
31ce2453c82fatagen2026-09-24 13:07 UTC1175
            opening: vec![reply],
31ce2453c82fatagen2026-09-24 13:07 UTC1176
        };
31ce2453c82fatagen2026-09-24 13:07 UTC1177
        self.rooms
31ce2453c82fatagen2026-09-24 13:07 UTC1178
            .insert(room_id.clone(), RoomSlot::Active { generation, sender });
31ce2453c82fatagen2026-09-24 13:07 UTC1179
        self.tasks.spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC1180
            TaskContext::RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC1181
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC1182
                generation,
31ce2453c82fatagen2026-09-24 13:07 UTC1183
            },
31ce2453c82fatagen2026-09-24 13:07 UTC1184
            actor.run(),
31ce2453c82fatagen2026-09-24 13:07 UTC1185
        );
5a52012c9b1datagen2026-09-24 23:45 UTC1186
        self.update_room_subscriptions();
31ce2453c82fatagen2026-09-24 13:07 UTC1187
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1188
5a52012c9b1datagen2026-09-24 23:45 UTC1189
    async fn actor_note(&mut self, note: ActorNote) {
5a52012c9b1datagen2026-09-24 23:45 UTC1190
        let (room_id, generation) = match &note {
5a52012c9b1datagen2026-09-24 23:45 UTC1191
            ActorNote::Leaving {
5a52012c9b1datagen2026-09-24 23:45 UTC1192
                room_id,
5a52012c9b1datagen2026-09-24 23:45 UTC1193
                generation,
5a52012c9b1datagen2026-09-24 23:45 UTC1194
            }
5a52012c9b1datagen2026-09-24 23:45 UTC1195
            | ActorNote::Exited {
5a52012c9b1datagen2026-09-24 23:45 UTC1196
                room_id,
5a52012c9b1datagen2026-09-24 23:45 UTC1197
                generation,
5a52012c9b1datagen2026-09-24 23:45 UTC1198
                ..
5a52012c9b1datagen2026-09-24 23:45 UTC1199
            } => (room_id.clone(), *generation),
5a52012c9b1datagen2026-09-24 23:45 UTC1200
        };
31ce2453c82fatagen2026-09-24 13:07 UTC1201
        if self
31ce2453c82fatagen2026-09-24 13:07 UTC1202
            .rooms
31ce2453c82fatagen2026-09-24 13:07 UTC1203
            .get(&room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC1204
            .is_none_or(|slot| slot.generation() != generation)
31ce2453c82fatagen2026-09-24 13:07 UTC1205
        {
31ce2453c82fatagen2026-09-24 13:07 UTC1206
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC1207
        }
5a52012c9b1datagen2026-09-24 23:45 UTC1208
        let ActorNote::Exited { leftovers, .. } = note else {
5a52012c9b1datagen2026-09-24 23:45 UTC1209
            if let Some(slot @ RoomSlot::Active { .. }) = self.rooms.get_mut(&room_id) {
5a52012c9b1datagen2026-09-24 23:45 UTC1210
                *slot = RoomSlot::Closing {
5a52012c9b1datagen2026-09-24 23:45 UTC1211
                    generation,
5a52012c9b1datagen2026-09-24 23:45 UTC1212
                    pending: VecDeque::new(),
5a52012c9b1datagen2026-09-24 23:45 UTC1213
                };
5a52012c9b1datagen2026-09-24 23:45 UTC1214
                self.update_room_subscriptions();
5a52012c9b1datagen2026-09-24 23:45 UTC1215
            }
5a52012c9b1datagen2026-09-24 23:45 UTC1216
            return;
5a52012c9b1datagen2026-09-24 23:45 UTC1217
        };
5a52012c9b1datagen2026-09-24 23:45 UTC1218
        let pending = match self.rooms.remove(&room_id) {
5a52012c9b1datagen2026-09-24 23:45 UTC1219
            Some(RoomSlot::Closing { pending, .. }) => pending,
5a52012c9b1datagen2026-09-24 23:45 UTC1220
            _ => VecDeque::new(),
5a52012c9b1datagen2026-09-24 23:45 UTC1221
        };
5a52012c9b1datagen2026-09-24 23:45 UTC1222
        // leftovers were sent before anything deferred, so they go first
5a52012c9b1datagen2026-09-24 23:45 UTC1223
        for request in leftovers {
5a52012c9b1datagen2026-09-24 23:45 UTC1224
            match request {
5a52012c9b1datagen2026-09-24 23:45 UTC1225
                ActorRequest::Open(reply) => self.open_room(room_id.clone(), reply).await,
5a52012c9b1datagen2026-09-24 23:45 UTC1226
                ActorRequest::Close(reply) => reply.done(Ok(())),
5a52012c9b1datagen2026-09-24 23:45 UTC1227
                ActorRequest::Execute(command, reply) => self.route_timeline(command, reply),
80381e06a573atagen2026-10-25 03:02 UTC1228
                ActorRequest::Repair(command, reply) => self.route_repair(command, reply),
31ce2453c82fatagen2026-09-24 13:07 UTC1229
            }
31ce2453c82fatagen2026-09-24 13:07 UTC1230
        }
5a52012c9b1datagen2026-09-24 23:45 UTC1231
        for deferred in pending {
5a52012c9b1datagen2026-09-24 23:45 UTC1232
            match deferred {
5a52012c9b1datagen2026-09-24 23:45 UTC1233
                Deferred::Open(reply) => self.open_room(room_id.clone(), reply).await,
5a52012c9b1datagen2026-09-24 23:45 UTC1234
                Deferred::Close(reply) => self.close_room(room_id.clone(), reply).await,
5a52012c9b1datagen2026-09-24 23:45 UTC1235
            }
5a52012c9b1datagen2026-09-24 23:45 UTC1236
        }
5a52012c9b1datagen2026-09-24 23:45 UTC1237
        self.update_room_subscriptions();
31ce2453c82fatagen2026-09-24 13:07 UTC1238
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1239
5a52012c9b1datagen2026-09-24 23:45 UTC1240
    fn update_room_subscriptions(&self) {
31ce2453c82fatagen2026-09-24 13:07 UTC1241
        let room_ids = self
31ce2453c82fatagen2026-09-24 13:07 UTC1242
            .rooms
31ce2453c82fatagen2026-09-24 13:07 UTC1243
            .iter()
31ce2453c82fatagen2026-09-24 13:07 UTC1244
            .filter(|(_, slot)| matches!(slot, RoomSlot::Active { .. }))
5a52012c9b1datagen2026-09-24 23:45 UTC1245
            .map(|(room_id, _)| room_id.clone())
5a52012c9b1datagen2026-09-24 23:45 UTC1246
            .collect();
5a52012c9b1datagen2026-09-24 23:45 UTC1247
        self.subscriptions.send_replace(room_ids);
31ce2453c82fatagen2026-09-24 13:07 UTC1248
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1249
31ce2453c82fatagen2026-09-24 13:07 UTC1250
    async fn shutdown(&mut self) {
5a52012c9b1datagen2026-09-24 23:45 UTC1251
        if let Some(handler) = self.verification_handler.take() {
5a52012c9b1datagen2026-09-24 23:45 UTC1252
            self.client.remove_event_handler(handler);
5a52012c9b1datagen2026-09-24 23:45 UTC1253
        }
31ce2453c82fatagen2026-09-24 13:07 UTC1254
        self.port.close();
3dab4a4e73f4atagen2026-10-28 06:21 UTC1255
        tracing::debug!(step = "sync", "engine shutdown step");
cbb715e11963atagen2026-10-24 23:24 UTC1256
        self.sync.stop().await;
cbb715e11963atagen2026-10-24 23:24 UTC1257
        if self.mentions_service.is_none() {
3dab4a4e73f4atagen2026-10-28 06:21 UTC1258
            tracing::debug!(step = "mentions", "engine shutdown step");
a912b21d0811atagen2026-10-28 07:52 UTC1259
            stop_within(self.mentions.stop(), "mentions").await;
cbb715e11963atagen2026-10-24 23:24 UTC1260
        }
c9effeac326aatagen2026-10-25 10:32 UTC1261
        if self.search_service.is_none() {
3dab4a4e73f4atagen2026-10-28 06:21 UTC1262
            tracing::debug!(step = "search", "engine shutdown step");
a912b21d0811atagen2026-10-28 07:52 UTC1263
            stop_within(self.search.stop(), "search").await;
c9effeac326aatagen2026-10-25 10:32 UTC1264
        }
3dab4a4e73f4atagen2026-10-28 06:21 UTC1265
        tracing::debug!(step = "tasks", "engine shutdown step");
31ce2453c82fatagen2026-09-24 13:07 UTC1266
        self.tasks.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC1267
        self.rooms.clear();
3dab4a4e73f4atagen2026-10-28 06:21 UTC1268
        tracing::debug!(step = "contexts", "engine shutdown step");
da8896938cf0atagen2026-10-25 10:04 UTC1269
        self.contexts.clear().await;
3dab4a4e73f4atagen2026-10-28 06:21 UTC1270
        tracing::debug!(step = "media", "engine shutdown step");
31ce2453c82fatagen2026-09-24 13:07 UTC1271
        self.media.clear().await;
3dab4a4e73f4atagen2026-10-28 06:21 UTC1272
        tracing::debug!(step = "done", "engine shutdown step");
31ce2453c82fatagen2026-09-24 13:07 UTC1273
        self.port
31ce2453c82fatagen2026-09-24 13:07 UTC1274
            .connection_sender()
31ce2453c82fatagen2026-09-24 13:07 UTC1275
            .send_replace(ConnectionState::Stopped);
31ce2453c82fatagen2026-09-24 13:07 UTC1276
        let _ = self
31ce2453c82fatagen2026-09-24 13:07 UTC1277
            .port
31ce2453c82fatagen2026-09-24 13:07 UTC1278
            .event_sender()
31ce2453c82fatagen2026-09-24 13:07 UTC1279
            .send(Event::ConnectionChanged(ConnectionState::Stopped))
31ce2453c82fatagen2026-09-24 13:07 UTC1280
            .await;
31ce2453c82fatagen2026-09-24 13:07 UTC1281
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1282
}
31ce2453c82fatagen2026-09-24 13:07 UTC1283
31ce2453c82fatagen2026-09-24 13:07 UTC1284
fn send_actor(sender: &mpsc::Sender<ActorRequest>, request: ActorRequest) {
31ce2453c82fatagen2026-09-24 13:07 UTC1285
    if let Err(error) = sender.try_send(request) {
31ce2453c82fatagen2026-09-24 13:07 UTC1286
        let (request, error) = match error {
31ce2453c82fatagen2026-09-24 13:07 UTC1287
            mpsc::error::TrySendError::Full(request) => (
31ce2453c82fatagen2026-09-24 13:07 UTC1288
                request,
31ce2453c82fatagen2026-09-24 13:07 UTC1289
                Error::new(ErrorKind::RoomBusy, "room command queue is full"),
31ce2453c82fatagen2026-09-24 13:07 UTC1290
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC1291
            mpsc::error::TrySendError::Closed(request) => (
31ce2453c82fatagen2026-09-24 13:07 UTC1292
                request,
31ce2453c82fatagen2026-09-24 13:07 UTC1293
                Error::new(ErrorKind::RoomNotOpen, "room is not open"),
31ce2453c82fatagen2026-09-24 13:07 UTC1294
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC1295
        };
31ce2453c82fatagen2026-09-24 13:07 UTC1296
        request.into_reply().done(Err(error));
31ce2453c82fatagen2026-09-24 13:07 UTC1297
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1298
}
31ce2453c82fatagen2026-09-24 13:07 UTC1299
31ce2453c82fatagen2026-09-24 13:07 UTC1300
fn defer(pending: &mut VecDeque<Deferred>, deferred: Deferred, capacity: usize) {
31ce2453c82fatagen2026-09-24 13:07 UTC1301
    if pending.len() < capacity {
31ce2453c82fatagen2026-09-24 13:07 UTC1302
        return pending.push_back(deferred);
31ce2453c82fatagen2026-09-24 13:07 UTC1303
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1304
    let (Deferred::Open(reply) | Deferred::Close(reply)) = deferred;
31ce2453c82fatagen2026-09-24 13:07 UTC1305
    reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC1306
        ErrorKind::RoomBusy,
31ce2453c82fatagen2026-09-24 13:07 UTC1307
        "room lifecycle queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC1308
    )));
31ce2453c82fatagen2026-09-24 13:07 UTC1309
}
31ce2453c82fatagen2026-09-24 13:07 UTC1310
673563a76e43atagen2026-09-26 12:30 UTC1311
async fn settled(logouts: &mut watch::Receiver<Logouts>) {
673563a76e43atagen2026-09-26 12:30 UTC1312
    let _ = logouts.wait_for(|logouts| logouts.in_flight == 0).await;
673563a76e43atagen2026-09-26 12:30 UTC1313
}
673563a76e43atagen2026-09-26 12:30 UTC1314
31ce2453c82fatagen2026-09-24 13:07 UTC1315
fn enqueue<T>(sender: &mpsc::Sender<Job<T>>, job: Job<T>) {
31ce2453c82fatagen2026-09-24 13:07 UTC1316
    if let Err(error) = sender.try_send(job) {
31ce2453c82fatagen2026-09-24 13:07 UTC1317
        let (job, error) = match error {
31ce2453c82fatagen2026-09-24 13:07 UTC1318
            mpsc::error::TrySendError::Full(job) => (
31ce2453c82fatagen2026-09-24 13:07 UTC1319
                job,
31ce2453c82fatagen2026-09-24 13:07 UTC1320
                Error::new(ErrorKind::QueueFull, "command queue is full"),
31ce2453c82fatagen2026-09-24 13:07 UTC1321
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC1322
            mpsc::error::TrySendError::Closed(job) => (job, Error::interrupted()),
31ce2453c82fatagen2026-09-24 13:07 UTC1323
        };
31ce2453c82fatagen2026-09-24 13:07 UTC1324
        job.reply.done(Err(error));
31ce2453c82fatagen2026-09-24 13:07 UTC1325
    }
31ce2453c82fatagen2026-09-24 13:07 UTC1326
}