atagen

macltok

public
Star0
Watch0
31ce2453c82f470aa8b5d3861ab0eec4273afc5d
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/engine/mod.rsRust776 lines26.4 KB
1
mod commands;
2
mod media;
3
mod mutations;
4
mod room;
5
mod timeline;
6
mod watchers;
7
8
use std::{
9
    collections::{HashMap, VecDeque},
10
    future::Future,
11
    sync::Arc,
12
};
13
14
use futures_util::future::BoxFuture;
15
use matrix_sdk::{Client, SessionChange};
16
use matrix_sdk_ui::sync_service::SyncService;
17
use ruma::{OwnedRoomId, RoomId};
18
use thiserror::Error as ThisError;
19
use tokio::{
20
    sync::{Notify, broadcast, mpsc, watch},
21
    task::{AbortHandle, Id as TaskId, JoinError, JoinSet},
22
};
23
24
pub(crate) use media::MediaService;
25
26
use self::{
27
    commands::Job,
28
    mutations::{Mutation, MutationJob},
29
    room::{ActorRequest, Exited, RoomActor},
30
    timeline::{SessionContext, Supervisor},
31
};
32
use crate::{
33
    AccountCommand, ClientHandle, Command, ConnectionState, EncryptionCommand, EngineErrorKind,
34
    EngineFailure, Error, ErrorKind, Event, EventStream, LifecycleCommand, MediaCommand,
35
    NotificationCommand, RoomCommand, TimelineCommand,
36
    transport::{DriverPort, Request, Responder, transport_pair},
37
};
38
39
#[derive(Clone, Debug, Eq, PartialEq)]
40
#[non_exhaustive]
41
pub struct EngineConfig {
42
    pub command_capacity: usize,
43
    pub event_capacity: usize,
44
    pub initial_history_limit: u16,
45
    pub max_media_bytes: usize,
46
    pub max_media_sources: usize,
47
}
48
49
impl EngineConfig {
50
    #[must_use]
51
    pub const fn with_command_capacity(mut self, capacity: usize) -> Self {
52
        self.command_capacity = capacity;
53
        self
54
    }
55
56
    #[must_use]
57
    pub const fn with_event_capacity(mut self, capacity: usize) -> Self {
58
        self.event_capacity = capacity;
59
        self
60
    }
61
62
    #[must_use]
63
    pub const fn with_initial_history_limit(mut self, limit: u16) -> Self {
64
        self.initial_history_limit = limit;
65
        self
66
    }
67
68
    #[must_use]
69
    pub const fn with_max_media_bytes(mut self, bytes: usize) -> Self {
70
        self.max_media_bytes = bytes;
71
        self
72
    }
73
74
    #[must_use]
75
    pub const fn with_max_media_sources(mut self, sources: usize) -> Self {
76
        self.max_media_sources = sources;
77
        self
78
    }
79
}
80
81
impl Default for EngineConfig {
82
    fn default() -> Self {
83
        Self {
84
            command_capacity: 64,
85
            event_capacity: 128,
86
            initial_history_limit: 30,
87
            max_media_bytes: 20 * 1024 * 1024,
88
            max_media_sources: 2_048,
89
        }
90
    }
91
}
92
93
#[derive(Debug, ThisError)]
94
pub enum EngineError {
95
    #[error("the Matrix client is not authenticated")]
96
    Unauthenticated,
97
    #[error("command, event, and media source capacities must be nonzero")]
98
    ZeroCapacity,
99
    #[error("could not build the sync service")]
100
    SyncService(#[source] matrix_sdk_ui::sync_service::Error),
101
    #[error("the event receiver has closed")]
102
    EventReceiverClosed,
103
    #[error("engine task {task} panicked")]
104
    TaskPanicked { task: String },
105
}
106
107
impl EngineError {
108
    fn failure(&self) -> EngineFailure {
109
        EngineFailure {
110
            kind: match self {
111
                Self::EventReceiverClosed => EngineErrorKind::EventReceiverClosed,
112
                Self::TaskPanicked { .. } => EngineErrorKind::Watcher,
113
                Self::Unauthenticated | Self::ZeroCapacity | Self::SyncService(_) => {
114
                    EngineErrorKind::Other
115
                }
116
            },
117
            message: self.to_string(),
118
        }
119
    }
120
}
121
122
#[derive(Clone)]
123
enum TaskContext {
124
    /// a panic here costs the caller an `Interrupted` answer and nothing else
125
    Command(&'static str),
126
    /// a panic here takes the engine with it
127
    Watcher(&'static str),
128
    RoomActor {
129
        room_id: OwnedRoomId,
130
        generation: u64,
131
    },
132
}
133
134
impl TaskContext {
135
    fn label(&self) -> String {
136
        match self {
137
            Self::Command(name) | Self::Watcher(name) => (*name).to_owned(),
138
            Self::RoomActor {
139
                room_id,
140
                generation,
141
            } => format!("room actor {room_id} generation {generation}"),
142
        }
143
    }
144
}
145
146
struct TaskRegistry {
147
    tasks: JoinSet<()>,
148
    contexts: HashMap<TaskId, TaskContext>,
149
}
150
151
impl TaskRegistry {
152
    fn spawn(
153
        &mut self,
154
        context: TaskContext,
155
        task: impl Future<Output = ()> + Send + 'static,
156
    ) -> AbortHandle {
157
        let handle = self.tasks.spawn(task);
158
        self.contexts.insert(handle.id(), context);
159
        handle
160
    }
161
162
    async fn join_next(&mut self) -> Option<(TaskContext, Result<(), JoinError>)> {
163
        let result = self.tasks.join_next_with_id().await?;
164
        let id = match &result {
165
            Ok((id, ())) => *id,
166
            Err(error) => error.id(),
167
        };
168
        let context = self.contexts.remove(&id)?;
169
        Some((context, result.map(drop)))
170
    }
171
172
    async fn shutdown(&mut self) {
173
        self.tasks.abort_all();
174
        while self.tasks.join_next().await.is_some() {}
175
        self.contexts.clear();
176
    }
177
}
178
179
enum RoomSlot {
180
    Active {
181
        generation: u64,
182
        sender: mpsc::Sender<ActorRequest>,
183
    },
184
    /// opens and closes that arrive while the actor winds down, replayed in order once it's gone
185
    Closing {
186
        generation: u64,
187
        pending: VecDeque<Deferred>,
188
    },
189
}
190
191
impl RoomSlot {
192
    const fn generation(&self) -> u64 {
193
        match self {
194
            Self::Active { generation, .. } | Self::Closing { generation, .. } => *generation,
195
        }
196
    }
197
}
198
199
enum Deferred {
200
    Open(Responder),
201
    Close(Responder),
202
}
203
204
struct Queues {
205
    mutations: mpsc::Receiver<MutationJob>,
206
    notifications: mpsc::Receiver<Job<NotificationCommand>>,
207
    encryption: mpsc::Receiver<Job<EncryptionCommand>>,
208
    session_changes: broadcast::Receiver<SessionChange>,
209
}
210
211
pub struct Engine {
212
    client: Client,
213
    sync: Arc<SyncService>,
214
    port: DriverPort,
215
    config: EngineConfig,
216
    media: Arc<MediaService>,
217
    tasks: TaskRegistry,
218
    rooms: HashMap<OwnedRoomId, RoomSlot>,
219
    next_generation: u64,
220
    exits: mpsc::UnboundedReceiver<Exited>,
221
    exit_sender: mpsc::UnboundedSender<Exited>,
222
    mutations: mpsc::Sender<MutationJob>,
223
    notifications: mpsc::Sender<Job<NotificationCommand>>,
224
    encryption: mpsc::Sender<Job<EncryptionCommand>>,
225
    queues: Option<Queues>,
226
    /// one progress forwarder per room that has uploaded, and whether it's subscribed yet
227
    uploads: HashMap<OwnedRoomId, (AbortHandle, watch::Receiver<bool>)>,
228
    stop: Arc<Notify>,
229
}
230
231
impl Engine {
232
    /// the escape hatch for a client you built yourself. `Session` is the usual way in
233
    pub async fn from_client(
234
        client: Client,
235
        config: EngineConfig,
236
    ) -> Result<(Self, ClientHandle, EventStream), EngineError> {
237
        if client.user_id().is_none() {
238
            return Err(EngineError::Unauthenticated);
239
        }
240
        if config.command_capacity == 0
241
            || config.event_capacity == 0
242
            || config.max_media_sources == 0
243
        {
244
            return Err(EngineError::ZeroCapacity);
245
        }
246
        let session_changes = client.subscribe_to_session_changes();
247
        let sync = Arc::new(
248
            SyncService::builder(client.clone())
249
                .with_offline_mode()
250
                .build()
251
                .await
252
                .map_err(EngineError::SyncService)?,
253
        );
254
        let media = Arc::new(MediaService::new(
255
            client.clone(),
256
            config.max_media_bytes,
257
            config.max_media_sources,
258
        ));
259
        let (handle, port, events) = transport_pair(
260
            client.clone(),
261
            sync.clone(),
262
            media.clone(),
263
            config.command_capacity,
264
            config.event_capacity,
265
        );
266
        client.send_queue().enable_upload_progress(true);
267
        let (exit_sender, exits) = mpsc::unbounded_channel();
268
        let (mutations, mutation_queue) = mpsc::channel(config.command_capacity);
269
        let (notifications, notification_queue) = mpsc::channel(config.command_capacity);
270
        let (encryption, encryption_queue) = mpsc::channel(config.command_capacity);
271
        let engine = Self {
272
            client,
273
            sync,
274
            port,
275
            config,
276
            media,
277
            tasks: TaskRegistry {
278
                tasks: JoinSet::new(),
279
                contexts: HashMap::new(),
280
            },
281
            rooms: HashMap::new(),
282
            next_generation: 0,
283
            exits,
284
            exit_sender,
285
            mutations,
286
            notifications,
287
            encryption,
288
            queues: Some(Queues {
289
                mutations: mutation_queue,
290
                notifications: notification_queue,
291
                encryption: encryption_queue,
292
                session_changes,
293
            }),
294
            uploads: HashMap::new(),
295
            stop: Arc::new(Notify::new()),
296
        };
297
        Ok((engine, handle, events))
298
    }
299
300
    pub async fn run(mut self) -> Result<(), EngineError> {
301
        let events = self.port.event_sender();
302
        let user_id = self
303
            .client
304
            .user_id()
305
            .expect("authentication was checked during construction")
306
            .to_owned();
307
        self.port
308
            .connection_sender()
309
            .send_replace(ConnectionState::Connecting);
310
        for event in [
311
            Event::Authenticated { user_id },
312
            Event::ConnectionChanged(ConnectionState::Connecting),
313
        ] {
314
            if events.send(event).await.is_err() {
315
                self.shutdown().await;
316
                return Err(EngineError::EventReceiverClosed);
317
            }
318
        }
319
        self.start_tasks();
320
        self.sync.start().await;
321
322
        let mut failure = None;
323
        let mut shutdown_reply = None;
324
        loop {
325
            tokio::select! {
326
                biased;
327
                () = events.closed() => {
328
                    failure = Some(EngineError::EventReceiverClosed);
329
                    break;
330
                }
331
                () = self.stop.notified() => break,
332
                request = self.port.recv() => {
333
                    let Some(request) = request else { break };
334
                    if let Some(reply) = self.dispatch(request).await {
335
                        shutdown_reply = Some(reply);
336
                        break;
337
                    }
338
                }
339
                Some((context, result)) = self.tasks.join_next(), if !self.tasks.tasks.is_empty() => {
340
                    let Err(error) = result else { continue };
341
                    if !error.is_panic() {
342
                        continue;
343
                    }
344
                    if let TaskContext::Command(label) = context {
345
                        tracing::error!(label, %error, "command task panicked");
346
                        continue;
347
                    }
348
                    failure = Some(EngineError::TaskPanicked { task: context.label() });
349
                    break;
350
                }
351
                Some(exited) = self.exits.recv() => self.room_exited(exited).await,
352
            }
353
        }
354
355
        if let Some(error) = &failure {
356
            let _ = events.send(Event::Failed(error.failure())).await;
357
        }
358
        self.shutdown().await;
359
        let _ = events.send(Event::Stopped).await;
360
        if let Some(reply) = shutdown_reply {
361
            reply.done(Ok(()));
362
        }
363
        failure.map_or(Ok(()), Err)
364
    }
365
366
    fn start_tasks(&mut self) {
367
        let queues = self.queues.take().expect("engine tasks start once");
368
        let client = &self.client;
369
        let events = || self.port.event_sender();
370
        let watchers: [(&'static str, BoxFuture<'static, ()>); 9] = [
371
            (
372
                "session change watcher",
373
                Box::pin(watchers::session(
374
                    queues.session_changes,
375
                    events(),
376
                    self.stop.clone(),
377
                )),
378
            ),
379
            (
380
                "connection state watcher",
381
                Box::pin(watchers::connection(
382
                    self.sync.state(),
383
                    self.sync.clone(),
384
                    client.clone(),
385
                    events(),
386
                    self.port.connection_sender(),
387
                )),
388
            ),
389
            (
390
                "send queue watcher",
391
                Box::pin(watchers::send_queue_errors(
392
                    client.clone(),
393
                    client.send_queue().subscribe_errors(),
394
                    self.port.connection_sender().subscribe(),
395
                )),
396
            ),
397
            (
398
                "encryption state watcher",
399
                Box::pin(watchers::encryption(client.clone(), events())),
400
            ),
401
            (
402
                "room list watcher",
403
                Box::pin(watchers::rooms(client.clone(), events())),
404
            ),
405
            (
406
                "notification settings watcher",
407
                Box::pin(watchers::notification_settings(client.clone(), events())),
408
            ),
409
            (
410
                "room command worker",
411
                Box::pin(mutations::run_scheduler(
412
                    client.clone(),
413
                    queues.mutations,
414
                    self.config.command_capacity,
415
                )),
416
            ),
417
            (
418
                "notification command worker",
419
                Box::pin(commands::run_notifications(
420
                    client.clone(),
421
                    queues.notifications,
422
                )),
423
            ),
424
            (
425
                "encryption command worker",
426
                Box::pin(commands::run_encryption(
427
                    client.clone(),
428
                    events(),
429
                    queues.encryption,
430
                )),
431
            ),
432
        ];
433
        for (label, task) in watchers {
434
            self.tasks.spawn(TaskContext::Watcher(label), task);
435
        }
436
    }
437
438
    /// hands back the shutdown request's responder, which is answered once we've stopped
439
    async fn dispatch(&mut self, Request { command, reply }: Request) -> Option<Responder> {
440
        let client = self.client.clone();
441
        match command {
442
            Command::Lifecycle(LifecycleCommand::Shutdown) => return Some(reply),
443
            Command::Account(AccountCommand::Logout) => {
444
                let events = self.port.event_sender();
445
                let stop = self.stop.clone();
446
                self.spawn_command("logout", reply, async move {
447
                    client
448
                        .logout()
449
                        .await
450
                        .map_err(|source| Error::from_sdk("logout failed", source))?;
451
                    let _ = events.send(Event::LoggedOut).await;
452
                    stop.notify_one();
453
                    Ok(())
454
                });
455
            }
456
            Command::Account(AccountCommand::SetAccountData {
457
                event_type,
458
                content,
459
            }) => enqueue(
460
                &self.mutations,
461
                Job {
462
                    command: Mutation::GlobalAccountData {
463
                        event_type,
464
                        content,
465
                    },
466
                    reply,
467
                },
468
            ),
469
            Command::Account(AccountCommand::SetDisplayName(name)) => {
470
                self.spawn_command("set display name", reply, async move {
471
                    client
472
                        .account()
473
                        .set_display_name(name.as_deref())
474
                        .await
475
                        .map_err(|source| Error::from_sdk("setting display name failed", source))
476
                });
477
            }
478
            Command::Account(AccountCommand::SetAvatar(avatar)) => {
479
                self.spawn_command("set avatar", reply, async move {
480
                    client
481
                        .account()
482
                        .set_avatar_url(avatar.as_deref())
483
                        .await
484
                        .map_err(|source| Error::from_sdk("setting avatar failed", source))
485
                });
486
            }
487
            Command::Account(AccountCommand::UploadAvatar { mimetype, bytes }) => {
488
                let max_bytes = self.config.max_media_bytes;
489
                self.spawn_command("upload avatar", reply, async move {
490
                    commands::upload_avatar(&client, &mimetype, bytes, max_bytes).await
491
                });
492
            }
493
            Command::Account(AccountCommand::RenameDevice {
494
                device_id,
495
                display_name,
496
            }) => self.spawn_command("rename device", reply, async move {
497
                commands::rename_device(&client, &device_id, &display_name).await
498
            }),
499
            Command::Account(AccountCommand::DeleteDevices { device_ids, auth }) => {
500
                self.spawn_command("delete devices", reply, async move {
501
                    commands::delete_devices(&client, &device_ids, auth).await
502
                });
503
            }
504
            Command::Room(RoomCommand::Open(room_id)) => self.open_room(room_id, reply).await,
505
            Command::Room(RoomCommand::Close(room_id)) => self.close_room(room_id, reply).await,
506
            Command::Room(RoomCommand::SetTyping { room_id, typing }) => {
507
                self.spawn_command("typing", reply, async move {
508
                    commands::set_typing(&client, &room_id, typing).await
509
                });
510
            }
511
            Command::Room(RoomCommand::CancelSend {
512
                room_id,
513
                transaction_id,
514
            }) => self.spawn_command("cancel send", reply, async move {
515
                commands::cancel_send(&client, &room_id, &transaction_id).await
516
            }),
517
            Command::Room(RoomCommand::RetrySend {
518
                room_id,
519
                transaction_id,
520
            }) => self.spawn_command("retry send", reply, async move {
521
                commands::retry_send(&client, &room_id, &transaction_id).await
522
            }),
523
            Command::Room(command) => enqueue(
524
                &self.mutations,
525
                Job {
526
                    command: Mutation::Room(command),
527
                    reply,
528
                },
529
            ),
530
            Command::Timeline(command) => self.route_timeline(command, reply),
531
            Command::Media(MediaCommand::Upload(upload)) => self.upload(upload, reply),
532
            Command::Encryption(command) => enqueue(&self.encryption, Job { command, reply }),
533
            Command::Notification(command) => {
534
                enqueue(&self.notifications, Job { command, reply });
535
            }
536
        }
537
        None
538
    }
539
540
    fn spawn_command(
541
        &mut self,
542
        label: &'static str,
543
        reply: Responder,
544
        work: impl Future<Output = Result<(), Error>> + Send + 'static,
545
    ) {
546
        self.tasks.spawn(TaskContext::Command(label), async move {
547
            reply.done(work.await);
548
        });
549
    }
550
551
    fn upload(&mut self, upload: crate::AttachmentUpload, reply: Responder) {
552
        let Some(room) = self.client.get_room(upload.timeline_id.room_id()) else {
553
            return reply.done(Err(Error::room_unknown()));
554
        };
555
        let room_id = room.room_id().to_owned();
556
        self.uploads
557
            .retain(|_, (forwarder, _)| !forwarder.is_finished());
558
        let mut ready = match self.uploads.get(&room_id) {
559
            Some((_, ready)) => ready.clone(),
560
            None => {
561
                let (subscribed, ready) = watch::channel(false);
562
                let forwarder = self.tasks.spawn(
563
                    TaskContext::Command("upload watcher"),
564
                    watchers::room_uploads(room.clone(), self.port.event_sender(), subscribed),
565
                );
566
                self.uploads.insert(room_id, (forwarder, ready.clone()));
567
                ready
568
            }
569
        };
570
        let max_bytes = self.config.max_media_bytes;
571
        // queueing before the forwarder listens would lose the first progress ticks
572
        self.spawn_command("upload", reply, async move {
573
            ready
574
                .wait_for(|subscribed| *subscribed)
575
                .await
576
                .map_err(|_| Error::new(ErrorKind::Store, "reading the media send queue failed"))?;
577
            media::queue_upload(room, upload, max_bytes).await
578
        });
579
    }
580
581
    fn route_timeline(&mut self, command: TimelineCommand, reply: Responder) {
582
        let sender = match self.rooms.get(command.room_id()) {
583
            Some(RoomSlot::Active { sender, .. }) => Some(sender.clone()),
584
            _ => None,
585
        };
586
        match (sender, command) {
587
            (Some(sender), command) => send_actor(&sender, ActorRequest::Execute(command, reply)),
588
            (
589
                None,
590
                TimelineCommand::MarkRead {
591
                    timeline_id,
592
                    event_id,
593
                },
594
            ) => {
595
                let client = self.client.clone();
596
                self.spawn_command("mark read", reply, async move {
597
                    commands::mark_read(&client, &timeline_id, event_id).await
598
                });
599
            }
600
            (None, _) => reply.done(Err(Error::new(ErrorKind::RoomNotOpen, "room is not open"))),
601
        }
602
    }
603
604
    async fn open_room(&mut self, room_id: OwnedRoomId, reply: Responder) {
605
        match self.rooms.get_mut(&room_id) {
606
            Some(RoomSlot::Active { sender, .. }) => send_actor(sender, ActorRequest::Open(reply)),
607
            Some(RoomSlot::Closing { pending, .. }) => {
608
                defer(pending, Deferred::Open(reply), self.config.command_capacity);
609
            }
610
            None => self.start_actor(room_id, reply).await,
611
        }
612
    }
613
614
    async fn close_room(&mut self, room_id: OwnedRoomId, reply: Responder) {
615
        let (generation, sender) = match self.rooms.get_mut(&room_id) {
616
            None => return reply.done(Ok(())),
617
            Some(RoomSlot::Closing { pending, .. }) => {
618
                return defer(
619
                    pending,
620
                    Deferred::Close(reply),
621
                    self.config.command_capacity,
622
                );
623
            }
624
            Some(RoomSlot::Active { generation, sender }) => (*generation, sender.clone()),
625
        };
626
        match sender.try_send(ActorRequest::Close(reply)) {
627
            // a closed channel means the actor already left on its own, which is closed enough
628
            Ok(()) | Err(mpsc::error::TrySendError::Closed(_)) => {}
629
            Err(mpsc::error::TrySendError::Full(request)) => {
630
                return request.into_reply().done(Err(Error::new(
631
                    ErrorKind::RoomBusy,
632
                    "room command queue is full",
633
                )));
634
            }
635
        }
636
        self.rooms.insert(
637
            room_id,
638
            RoomSlot::Closing {
639
                generation,
640
                pending: VecDeque::new(),
641
            },
642
        );
643
        self.update_room_subscriptions().await;
644
    }
645
646
    async fn start_actor(&mut self, room_id: OwnedRoomId, reply: Responder) {
647
        let Some(room) = self.client.get_room(&room_id) else {
648
            return reply.done(Err(Error::room_unknown()));
649
        };
650
        self.next_generation = self.next_generation.wrapping_add(1);
651
        let generation = self.next_generation;
652
        let (sender, requests) = mpsc::channel(self.config.command_capacity);
653
        let (supervisor, panics) = Supervisor::new();
654
        let actor = RoomActor {
655
            room_id: room_id.clone(),
656
            generation,
657
            room,
658
            requests,
659
            exits: self.exit_sender.clone(),
660
            context: SessionContext {
661
                events: self.port.event_sender(),
662
                media: self.media.clone(),
663
                current_user_id: self.client.user_id().map(ToOwned::to_owned),
664
                initial_history_limit: self.config.initial_history_limit,
665
                supervisor,
666
            },
667
            panics,
668
            opening: vec![reply],
669
        };
670
        self.rooms
671
            .insert(room_id.clone(), RoomSlot::Active { generation, sender });
672
        self.tasks.spawn(
673
            TaskContext::RoomActor {
674
                room_id,
675
                generation,
676
            },
677
            actor.run(),
678
        );
679
        self.update_room_subscriptions().await;
680
    }
681
682
    async fn room_exited(
683
        &mut self,
684
        Exited {
685
            room_id,
686
            generation,
687
        }: Exited,
688
    ) {
689
        if self
690
            .rooms
691
            .get(&room_id)
692
            .is_none_or(|slot| slot.generation() != generation)
693
        {
694
            return;
695
        }
696
        if let Some(RoomSlot::Closing { pending, .. }) = self.rooms.remove(&room_id) {
697
            for deferred in pending {
698
                match deferred {
699
                    Deferred::Open(reply) => self.open_room(room_id.clone(), reply).await,
700
                    Deferred::Close(reply) => self.close_room(room_id.clone(), reply).await,
701
                }
702
            }
703
        }
704
        self.update_room_subscriptions().await;
705
    }
706
707
    async fn update_room_subscriptions(&self) {
708
        let room_ids = self
709
            .rooms
710
            .iter()
711
            .filter(|(_, slot)| matches!(slot, RoomSlot::Active { .. }))
712
            .map(|(room_id, _)| room_id.as_ref())
713
            .collect::<Vec<&RoomId>>();
714
        self.sync
715
            .room_list_service()
716
            .subscribe_to_rooms(&room_ids)
717
            .await;
718
    }
719
720
    async fn shutdown(&mut self) {
721
        self.port.close();
722
        self.tasks.shutdown().await;
723
        self.rooms.clear();
724
        self.uploads.clear();
725
        self.media.clear().await;
726
        self.sync.stop().await;
727
        self.port
728
            .connection_sender()
729
            .send_replace(ConnectionState::Stopped);
730
        let _ = self
731
            .port
732
            .event_sender()
733
            .send(Event::ConnectionChanged(ConnectionState::Stopped))
734
            .await;
735
    }
736
}
737
738
fn send_actor(sender: &mpsc::Sender<ActorRequest>, request: ActorRequest) {
739
    if let Err(error) = sender.try_send(request) {
740
        let (request, error) = match error {
741
            mpsc::error::TrySendError::Full(request) => (
742
                request,
743
                Error::new(ErrorKind::RoomBusy, "room command queue is full"),
744
            ),
745
            mpsc::error::TrySendError::Closed(request) => (
746
                request,
747
                Error::new(ErrorKind::RoomNotOpen, "room is not open"),
748
            ),
749
        };
750
        request.into_reply().done(Err(error));
751
    }
752
}
753
754
fn defer(pending: &mut VecDeque<Deferred>, deferred: Deferred, capacity: usize) {
755
    if pending.len() < capacity {
756
        return pending.push_back(deferred);
757
    }
758
    let (Deferred::Open(reply) | Deferred::Close(reply)) = deferred;
759
    reply.done(Err(Error::new(
760
        ErrorKind::RoomBusy,
761
        "room lifecycle queue is full",
762
    )));
763
}
764
765
fn enqueue<T>(sender: &mpsc::Sender<Job<T>>, job: Job<T>) {
766
    if let Err(error) = sender.try_send(job) {
767
        let (job, error) = match error {
768
            mpsc::error::TrySendError::Full(job) => (
769
                job,
770
                Error::new(ErrorKind::QueueFull, "command queue is full"),
771
            ),
772
            mpsc::error::TrySendError::Closed(job) => (job, Error::interrupted()),
773
        };
774
        job.reply.done(Err(error));
775
    }
776
}