atagen

macltok

public
Star0
Watch0
80381e06a573f253c11a8b4c89366a9aa299ca55
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/engine/room.rsRust578 lines19.9 KB
1
use std::collections::{HashMap, VecDeque};
2
3
use matrix_sdk::{Room, RoomState};
4
use matrix_sdk_ui::timeline::{RoomExt, Timeline, TimelineFocus, TimelineReadReceiptTracking};
5
use ruma::OwnedRoomId;
6
use tokio::sync::mpsc;
7
8
use super::executor::{AbortHandle, JoinError, TaskSet, is_panic, resume_unwind};
9
use super::repair::{InstallRequest, RepairController};
10
use super::timeline::{SessionContext, TaskGuard, TimelineSession};
11
use crate::{
12
    Error, ErrorKind, Event, HistoryRepairCommand, TimelineCommand, TimelineId, TypingState,
13
    transport::Responder,
14
};
15
16
pub(super) enum ActorRequest {
17
    /// opening an open room answers with a fresh reset
18
    Open(Responder),
19
    Execute(TimelineCommand, Responder),
20
    Repair(HistoryRepairCommand, Responder),
21
    Close(Responder),
22
}
23
24
impl ActorRequest {
25
    pub(super) fn into_reply(self) -> Responder {
26
        match self {
27
            Self::Open(reply)
28
            | Self::Execute(_, reply)
29
            | Self::Repair(_, reply)
30
            | Self::Close(reply) => reply,
31
        }
32
    }
33
}
34
35
pub(super) enum ActorNote {
36
    /// stopping on its own, after a leave or a kick. anything sent its way now comes back in `Exited`
37
    Leaving {
38
        room_id: OwnedRoomId,
39
        generation: u64,
40
    },
41
    Exited {
42
        room_id: OwnedRoomId,
43
        generation: u64,
44
        /// requests that arrived too late to be served, for the engine to answer or replay
45
        leftovers: Vec<ActorRequest>,
46
    },
47
}
48
49
pub(super) struct RoomActor {
50
    pub(super) room_id: OwnedRoomId,
51
    pub(super) generation: u64,
52
    pub(super) room: Room,
53
    pub(super) requests: mpsc::Receiver<ActorRequest>,
54
    pub(super) notes: mpsc::UnboundedSender<ActorNote>,
55
    pub(super) context: SessionContext,
56
    pub(super) panics: mpsc::UnboundedReceiver<&'static str>,
57
    pub(super) opening: Vec<Responder>,
58
}
59
60
impl RoomActor {
61
    pub(super) async fn run(mut self) {
62
        self.serve().await;
63
        self.requests.close();
64
        let mut leftovers = Vec::new();
65
        while let Ok(request) = self.requests.try_recv() {
66
            leftovers.push(request);
67
        }
68
        let _ = self.notes.send(ActorNote::Exited {
69
            room_id: self.room_id.clone(),
70
            generation: self.generation,
71
            leftovers,
72
        });
73
    }
74
75
    async fn serve(&mut self) {
76
        let timeline = match build_timeline(
77
            &self.room,
78
            TimelineFocus::Live {
79
                hide_threaded_events: true,
80
            },
81
        )
82
        .await
83
        {
84
            Ok(timeline) => timeline,
85
            Err(error) => return self.refuse_everything(error).await,
86
        };
87
        let Ok(room_timeline) = TimelineSession::start(
88
            TimelineId::Room(self.room_id.clone()),
89
            timeline,
90
            self.context.clone(),
91
        )
92
        .await
93
        else {
94
            return;
95
        };
96
        for reply in self.opening.drain(..) {
97
            reply.done(Ok(()));
98
        }
99
        let (typing_guard, typing) = self.room.subscribe_to_typing_notifications();
100
        let mut typing_watcher = TaskGuard(self.context.supervisor.spawn(
101
            "typing watcher",
102
            watch_typing(self.room_id.clone(), typing, self.context.events.clone()),
103
        ));
104
        let mut timelines = Timelines {
105
            room: room_timeline,
106
            threads: Threads::new(self.requests.max_capacity()),
107
            work: TaskSet::new(),
108
        };
109
        let mut repair = RepairController::new(self.room.clone(), self.context.clone());
110
        let close = self.serve_open(&mut timelines, &mut repair).await;
111
        repair.shutdown().await;
112
        if close.is_none() {
113
            self.requests.close();
114
            let _ = self.notes.send(ActorNote::Leaving {
115
                room_id: self.room_id.clone(),
116
                generation: self.generation,
117
            });
118
        }
119
120
        // let redactions and receipts that already left finish rather than lie about them
121
        while timelines.work.join_next().await.is_some() {}
122
        drop(typing_guard);
123
        typing_watcher.0.abort();
124
        let _ = (&mut typing_watcher.0).await;
125
        let _ = self
126
            .context
127
            .events
128
            .send(Event::TypingChanged(TypingState {
129
                room_id: self.room_id.clone(),
130
                users: Vec::new(),
131
            }))
132
            .await;
133
        timelines.threads.close().await;
134
        timelines.room.close().await;
135
        if let Some(reply) = close {
136
            reply.done(Ok(()));
137
        }
138
    }
139
140
    /// returns the close request's responder, if a close is why we stopped
141
    async fn serve_open(
142
        &mut self,
143
        timelines: &mut Timelines,
144
        repair: &mut RepairController,
145
    ) -> Option<Responder> {
146
        let mut room_info = self.room.subscribe_info();
147
        // a leave that landed while the timeline was building won't come round again
148
        if matches!(self.room.state(), RoomState::Left | RoomState::Banned) {
149
            return None;
150
        }
151
        loop {
152
            tokio::select! {
153
                Some(info) = room_info.next() => {
154
                    if matches!(info.state(), RoomState::Left | RoomState::Banned) {
155
                        tracing::debug!(room_id = %self.room_id, "room actor stopped because we left the room");
156
                        return None;
157
                    }
158
                }
159
                request = self.requests.recv() => match request {
160
                    None => return None,
161
                    Some(ActorRequest::Close(reply)) => return Some(reply),
162
                    Some(ActorRequest::Open(reply)) => reply.done(timelines.room.resync().await),
163
                    Some(ActorRequest::Execute(command, reply)) => {
164
                        timelines.execute(command, reply, &self.room).await;
165
                    }
166
                    Some(ActorRequest::Repair(command, reply)) => {
167
                        repair.execute(command, reply).await;
168
                    }
169
                },
170
                install = repair.next_install(), if repair.is_running() => {
171
                    if let Some(install) = install {
172
                        timelines.install_history(install, &self.room, &self.context).await;
173
                    }
174
                }
175
                Some(built) = timelines.threads.builds.join_next(), if !timelines.threads.builds.is_empty() => {
176
                    timelines.thread_built(built, &self.room_id, &self.context).await;
177
                }
178
                Some(label) = self.panics.recv() => panic!("{label} panicked in room {}", self.room_id),
179
                Some(done) = timelines.work.join_next(), if !timelines.work.is_empty() => {
180
                    if let Err(error) = done && is_panic(&error) {
181
                        tracing::warn!(room_id = %self.room_id, %error, "timeline operation panicked");
182
                    }
183
                }
184
            }
185
        }
186
    }
187
188
    async fn refuse_everything(&mut self, error: matrix_sdk_ui::timeline::Error) {
189
        tracing::warn!(%error, room_id = %self.room_id, "room actor could not build its timeline");
190
        let error = Error::with_source(ErrorKind::Timeline, "room timeline build failed", error);
191
        for reply in self.opening.drain(..) {
192
            reply.done(Err(error.clone()));
193
        }
194
        self.requests.close();
195
        while let Some(request) = self.requests.recv().await {
196
            request.into_reply().done(Err(error.clone()));
197
        }
198
    }
199
}
200
201
async fn build_timeline(
202
    room: &Room,
203
    focus: TimelineFocus,
204
) -> Result<Timeline, matrix_sdk_ui::timeline::Error> {
205
    room.timeline_builder()
206
        .with_focus(focus)
207
        .track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
208
        .build()
209
        .await
210
}
211
212
async fn watch_typing(
213
    room_id: OwnedRoomId,
214
    mut typing: tokio::sync::broadcast::Receiver<Vec<ruma::OwnedUserId>>,
215
    events: crate::transport::EventSender,
216
) {
217
    loop {
218
        match typing.recv().await {
219
            Ok(users) => {
220
                let state = TypingState {
221
                    room_id: room_id.clone(),
222
                    users,
223
                };
224
                if events.send(Event::TypingChanged(state)).await.is_err() {
225
                    return;
226
                }
227
            }
228
            Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
229
                tracing::warn!(%room_id, skipped, "typing watcher lagged");
230
            }
231
            Err(tokio::sync::broadcast::error::RecvError::Closed) => return,
232
        }
233
    }
234
}
235
236
struct Timelines {
237
    room: TimelineSession,
238
    threads: Threads,
239
    work: TaskSet<()>,
240
}
241
242
impl Timelines {
243
    async fn install_history(
244
        &mut self,
245
        install: InstallRequest,
246
        room: &Room,
247
        context: &SessionContext,
248
    ) {
249
        let InstallRequest {
250
            events,
251
            prev_batch,
252
            reply,
253
        } = install;
254
        let result = self
255
            .install_history_inner(events, prev_batch, room, context)
256
            .await;
257
        let _ = reply.send(result);
258
    }
259
260
    async fn install_history_inner(
261
        &mut self,
262
        events: Vec<matrix_sdk::deserialized_responses::TimelineEvent>,
263
        prev_batch: Option<String>,
264
        room: &Room,
265
        context: &SessionContext,
266
    ) -> Result<(), Error> {
267
        let threads_capacity = self.threads.capacity;
268
        self.threads.close().await;
269
        self.threads = Threads::new(threads_capacity);
270
        self.room.pause_for_reseed().await;
271
272
        let reseed = async {
273
            let (cache, _drop_handles) = room.event_cache().await.map_err(|source| {
274
                Error::with_source(ErrorKind::Timeline, "history event cache failed", source)
275
            })?;
276
            cache.reseed(events, prev_batch).await.map_err(|source| {
277
                Error::with_source(ErrorKind::Timeline, "history cache reseed failed", source)
278
            })
279
        }
280
        .await;
281
        if let Err(error) = reseed {
282
            return match self.room.resume_after_failed_reseed().await {
283
                Ok(()) => Err(error),
284
                Err(restart_error) => Err(restart_error),
285
            };
286
        }
287
288
        let timeline = match build_timeline(
289
            room,
290
            TimelineFocus::Live {
291
                hide_threaded_events: true,
292
            },
293
        )
294
        .await
295
        {
296
            Ok(timeline) => timeline,
297
            Err(source) => {
298
                let error =
299
                    Error::with_source(ErrorKind::Timeline, "room timeline rebuild failed", source);
300
                return match self.room.resume_after_failed_reseed().await {
301
                    Ok(()) => Err(error),
302
                    Err(restart_error) => Err(restart_error),
303
                };
304
            }
305
        };
306
        let timeline_id = self.room.id.clone();
307
        let mut context = context.clone();
308
        context.initial_history_limit = 0;
309
        let timeline = match TimelineSession::start(timeline_id, timeline, context).await {
310
            Ok(timeline) => timeline,
311
            Err(source) => {
312
                let error =
313
                    Error::with_source(ErrorKind::Timeline, "room timeline reset failed", source);
314
                return match self.room.resume_after_failed_reseed().await {
315
                    Ok(()) => Err(error),
316
                    Err(restart_error) => Err(restart_error),
317
                };
318
            }
319
        };
320
        let previous = std::mem::replace(&mut self.room, timeline);
321
        drop(previous);
322
        Ok(())
323
    }
324
325
    async fn execute(&mut self, command: TimelineCommand, reply: Responder, room: &Room) {
326
        match command {
327
            TimelineCommand::OpenThread {
328
                room_id,
329
                root_event_id,
330
            } => self
331
                .threads
332
                .open(TimelineId::thread(room_id, root_event_id), reply, room),
333
            TimelineCommand::CloseThread {
334
                room_id,
335
                root_event_id,
336
            } => {
337
                let id = TimelineId::thread(room_id, root_event_id);
338
                reply.done(self.threads.close_one(&id).await);
339
            }
340
            TimelineCommand::OpenPinned(room_id) => {
341
                self.threads.open(TimelineId::Pinned(room_id), reply, room);
342
            }
343
            TimelineCommand::ClosePinned(room_id) => {
344
                reply.done(self.threads.close_one(&TimelineId::Pinned(room_id)).await);
345
            }
346
            TimelineCommand::Resync {
347
                timeline_id: TimelineId::Room(_),
348
            } => reply.done(self.room.resync().await),
349
            command => {
350
                let Some(id) = command
351
                    .timeline_id()
352
                    .filter(|id| !matches!(id, TimelineId::Room(_)))
353
                    .cloned()
354
                else {
355
                    return self.room.execute(command, reply, &mut self.work).await;
356
                };
357
                if matches!(id, TimelineId::Pinned(_))
358
                    && matches!(
359
                        command,
360
                        TimelineCommand::Send { .. } | TimelineCommand::Paginate { .. }
361
                    )
362
                {
363
                    return reply.done(Err(Error::new(
364
                        ErrorKind::Invalid,
365
                        "the pinned timeline can't send or paginate",
366
                    )));
367
                }
368
                if let TimelineCommand::MarkRead {
369
                    timeline_id,
370
                    event_id,
371
                } = &command
372
                    && !self.threads.open.contains_key(&id)
373
                {
374
                    let client = room.client();
375
                    let (timeline_id, event_id) = (timeline_id.clone(), event_id.clone());
376
                    self.work.spawn(async move {
377
                        reply.done(
378
                            super::commands::mark_read(&client, &timeline_id, event_id).await,
379
                        );
380
                    });
381
                    return;
382
                }
383
                match self.threads.route(id, command, reply) {
384
                    Routed::Open(session, TimelineCommand::Resync { .. }, reply) => {
385
                        reply.done(session.resync().await);
386
                    }
387
                    Routed::Open(session, command, reply) => {
388
                        session.execute(command, reply, &mut self.work).await;
389
                    }
390
                    Routed::Queued => {}
391
                }
392
            }
393
        }
394
    }
395
396
    async fn thread_built(
397
        &mut self,
398
        built: Result<Built, JoinError>,
399
        room_id: &OwnedRoomId,
400
        context: &SessionContext,
401
    ) {
402
        let Some((id, queued, timeline)) = self.threads.built(built) else {
403
            return;
404
        };
405
        let timeline = match timeline {
406
            Ok(timeline) => timeline,
407
            Err(error) => {
408
                tracing::warn!(%error, %room_id, ?id, "timeline build failed");
409
                let error = Error::with_source(ErrorKind::Timeline, "timeline build failed", error);
410
                for (_, reply) in queued {
411
                    reply.done(Err(error.clone()));
412
                }
413
                return;
414
            }
415
        };
416
        let Ok(session) = TimelineSession::start(id.clone(), timeline, context.clone()).await
417
        else {
418
            return;
419
        };
420
        let session = self.threads.open.entry(id).insert_entry(session).into_mut();
421
        for (command, reply) in queued {
422
            match command {
423
                None => reply.done(Ok(())),
424
                Some(TimelineCommand::Resync { .. }) => reply.done(session.resync().await),
425
                Some(command) => session.execute(command, reply, &mut self.work).await,
426
            }
427
        }
428
    }
429
}
430
431
type Built = (
432
    TimelineId,
433
    u64,
434
    Result<Timeline, matrix_sdk_ui::timeline::Error>,
435
);
436
437
/// `None` is the open itself, everything else waits behind it
438
type Queued = VecDeque<(Option<TimelineCommand>, Responder)>;
439
440
enum Routed<'a> {
441
    Open(&'a mut TimelineSession, TimelineCommand, Responder),
442
    Queued,
443
}
444
445
struct OpeningThread {
446
    generation: u64,
447
    build: AbortHandle,
448
    queued: Queued,
449
}
450
451
/// threads and the pinned timeline, which build off the actor and queue what arrives meanwhile
452
struct Threads {
453
    open: HashMap<TimelineId, TimelineSession>,
454
    opening: HashMap<TimelineId, OpeningThread>,
455
    builds: TaskSet<Built>,
456
    next_generation: u64,
457
    queued: usize,
458
    capacity: usize,
459
}
460
461
impl Threads {
462
    fn new(capacity: usize) -> Self {
463
        Self {
464
            open: HashMap::new(),
465
            opening: HashMap::new(),
466
            builds: TaskSet::new(),
467
            next_generation: 0,
468
            queued: 0,
469
            capacity,
470
        }
471
    }
472
473
    fn enqueue(&mut self, id: &TimelineId, command: Option<TimelineCommand>, reply: Responder) {
474
        if self.queued >= self.capacity {
475
            return reply.done(Err(Error::new(
476
                ErrorKind::QueueFull,
477
                "thread command queue is full",
478
            )));
479
        }
480
        let Some(opening) = self.opening.get_mut(id) else {
481
            return reply.done(Err(Error::new(
482
                ErrorKind::TimelineNotOpen,
483
                "timeline is not open",
484
            )));
485
        };
486
        opening.queued.push_back((command, reply));
487
        self.queued += 1;
488
    }
489
490
    fn open(&mut self, id: TimelineId, reply: Responder, room: &Room) {
491
        if self.open.contains_key(&id) {
492
            return reply.done(Ok(()));
493
        }
494
        if !self.opening.contains_key(&id) {
495
            let generation = self.next_generation;
496
            self.next_generation = self.next_generation.wrapping_add(1);
497
            let room = room.clone();
498
            let build_id = id.clone();
499
            let build = self.builds.spawn(async move {
500
                let focus = match &build_id {
501
                    TimelineId::Thread { root_event_id, .. } => TimelineFocus::Thread {
502
                        root_event_id: root_event_id.clone(),
503
                    },
504
                    _ => TimelineFocus::PinnedEvents,
505
                };
506
                (build_id, generation, build_timeline(&room, focus).await)
507
            });
508
            self.opening.insert(
509
                id.clone(),
510
                OpeningThread {
511
                    generation,
512
                    build,
513
                    queued: VecDeque::new(),
514
                },
515
            );
516
        }
517
        self.enqueue(&id, None, reply);
518
    }
519
520
    fn route(&mut self, id: TimelineId, command: TimelineCommand, reply: Responder) -> Routed<'_> {
521
        if !self.open.contains_key(&id) {
522
            self.enqueue(&id, Some(command), reply);
523
            return Routed::Queued;
524
        }
525
        let session = self.open.get_mut(&id).expect("timeline was just checked");
526
        Routed::Open(session, command, reply)
527
    }
528
529
    /// `None` when the build belongs to a thread that was closed while it ran
530
    fn built(
531
        &mut self,
532
        built: Result<Built, JoinError>,
533
    ) -> Option<(
534
        TimelineId,
535
        Queued,
536
        Result<Timeline, matrix_sdk_ui::timeline::Error>,
537
    )> {
538
        let (id, generation, timeline) = match built {
539
            Ok(built) => built,
540
            Err(error) if error.is_cancelled() => return None,
541
            Err(error) => resume_unwind(error),
542
        };
543
        if self
544
            .opening
545
            .get(&id)
546
            .is_none_or(|opening| opening.generation != generation)
547
        {
548
            return None;
549
        }
550
        let opening = self.opening.remove(&id)?;
551
        self.queued -= opening.queued.len();
552
        Some((id, opening.queued, timeline))
553
    }
554
555
    async fn close_one(&mut self, id: &TimelineId) -> Result<(), Error> {
556
        if let Some(session) = self.open.remove(id) {
557
            session.close().await;
558
            return Ok(());
559
        }
560
        let Some(opening) = self.opening.remove(id) else {
561
            return Err(Error::new(
562
                ErrorKind::TimelineNotOpen,
563
                "timeline is not open",
564
            ));
565
        };
566
        opening.build.abort();
567
        self.queued -= opening.queued.len();
568
        Ok(())
569
    }
570
571
    async fn close(&mut self) {
572
        self.builds.shutdown().await;
573
        self.opening.clear();
574
        for (_, session) in self.open.drain() {
575
            session.close().await;
576
        }
577
    }
578
}