Commit 673563a76e43
tarball2 files changed+75-20
@@ -16,7 +16,7 @@ use std::{
1616
use futures_util::FutureExt;1717
use matrix_sdk::{Client, SendOutsideWasm, SessionChange};1818
use matrix_sdk_ui::sync_service::SyncService;19
-use ruma::OwnedRoomId;19
+use ruma::{OwnedDeviceId, OwnedRoomId};2020
use thiserror::Error as ThisError;2121
use tokio::sync::{broadcast, mpsc, watch};2222
@@ -214,6 +214,13 @@ enum Deferred {
214214
Close(Responder),215215
}216216
217
+/// logouts in flight, and a token rejection held back because one of them caused it218
+#[derive(Default)]219
+pub(super) struct Logouts {220
+ pub(super) in_flight: usize,221
+ pub(super) rejected: Option<Option<OwnedDeviceId>>,222
+}223
+217224
struct Queues {218225
mutations: mpsc::Receiver<MutationJob>,219226
notifications: mpsc::Receiver<Job<NotificationCommand>>,@@ -250,6 +257,7 @@ pub struct Engine {
250257
verification_handler: Option<matrix_sdk::event_handler::EventHandlerHandle>,251258
stop: mpsc::UnboundedSender<Termination>,252259
stops: mpsc::UnboundedReceiver<Termination>,260
+ logouts: Arc<watch::Sender<Logouts>>,253261
}254262
255263
impl Engine {@@ -323,6 +331,7 @@ impl Engine {
323331
verification_handler: None,324332
stop,325333
stops,334
+ logouts: Arc::new(watch::Sender::new(Logouts::default())),326335
};327336
Ok((engine, handle, events))328337
}@@ -357,11 +366,16 @@ impl Engine {
357366
self.sync.start().await;358367
359368
let mut shutdown_reply = None;369
+ let mut handles_dropped = false;370
+ let mut logouts = self.logouts.subscribe();360371
let ending = loop {361372
tokio::select! {362373
biased;363
- () = events.closed() => break Err(EngineError::EventReceiverClosed),364374
Some(termination) = self.stops.recv() => break Ok(termination),375
+ () = events.closed() => break Err(EngineError::EventReceiverClosed),376
+ () = settled(&mut logouts), if handles_dropped => {377
+ break Ok(Termination::HandlesDropped);378
+ }365379
Some((context, panic)) = self.tasks.join_next(), if !self.tasks.is_empty() => {366380
let Some(message) = panic else { continue };367381
if let TaskContext::Command(label) = context {@@ -371,8 +385,12 @@ impl Engine {
371385
break Err(EngineError::TaskPanicked { task: context.label() });372386
}373387
Some(note) = self.notes.recv() => self.actor_note(note).await,374
- request = self.port.recv() => {375
- let Some(request) = request else { break Ok(Termination::HandlesDropped) };388
+ request = self.port.recv(), if !handles_dropped => {389
+ // nobody's left to ask, but a logout they asked for still deserves to land390
+ let Some(request) = request else {391
+ handles_dropped = true;392
+ continue;393
+ };376394
if let Some(reply) = self.dispatch(request).await {377395
shutdown_reply = Some(reply);378396
break Ok(Termination::ShutDown);@@ -431,6 +449,7 @@ impl Engine {
431449
.expect("authentication was checked during construction")432450
.to_owned(),433451
self.stop.clone(),452
+ self.logouts.clone(),434453
)),435454
),436455
(@@ -525,14 +544,26 @@ impl Engine {
525544
Command::Account(AccountCommand::Logout) => {526545
let events = self.port.event_sender();527546
let stop = self.stop.clone();547
+ let logouts = self.logouts.clone();548
+ logouts.send_modify(|logouts| logouts.in_flight += 1);528549
self.spawn_command("logout", reply, async move {529
- client530
- .logout()531
- .await532
- .map_err(|source| Error::from_sdk("logout failed", source))?;533
- let _ = events.send(Event::LoggedOut).await;534
- let _ = stop.send(Termination::LoggedOut);535
- Ok(())550
+ let result = client.logout().await;551
+ if result.is_ok() {552
+ let _ = events.send(Event::LoggedOut).await;553
+ let _ = stop.send(Termination::LoggedOut);554
+ }555
+ let mut rejected = None;556
+ logouts.send_if_modified(|logouts| {557
+ if result.is_err() && logouts.in_flight == 1 {558
+ rejected = logouts.rejected.take();559
+ }560
+ false561
+ });562
+ if let Some(soft_logout) = rejected {563
+ watchers::reject_session(&events, &stop, soft_logout).await;564
+ }565
+ logouts.send_modify(|logouts| logouts.in_flight -= 1);566
+ result.map_err(|source| Error::from_sdk("logout failed", source))536567
});537568
}538569
Command::Account(AccountCommand::SetAccountData {@@ -883,6 +914,10 @@ fn defer(pending: &mut VecDeque<Deferred>, deferred: Deferred, capacity: usize)
883914
)));884915
}885916
917
+async fn settled(logouts: &mut watch::Receiver<Logouts>) {918
+ let _ = logouts.wait_for(|logouts| logouts.in_flight == 0).await;919
+}920
+886921
fn enqueue<T>(sender: &mpsc::Sender<Job<T>>, job: Job<T>) {887922
if let Err(error) = sender.try_send(job) {888923
let (job, error) = match error {@@ -10,7 +10,7 @@ use matrix_sdk_ui::sync_service::{State as SyncState, SyncService};
1010
use ruma::{OwnedDeviceId, OwnedRoomId, OwnedTransactionId, time::Instant};1111
use tokio::sync::{broadcast::error::RecvError, mpsc, watch};1212
13
-use super::executor::TaskSet;13
+use super::{Logouts, executor::TaskSet};1414
use crate::{1515
ConnectionState, Event, Termination, UploadOutcome, encryption::encryption_state,1616
error::classify_sdk_error, transport::EventSender,@@ -37,21 +37,25 @@ pub(super) async fn session(
3737
events: EventSender,3838
device_id: OwnedDeviceId,3939
stop: mpsc::UnboundedSender<Termination>,40
+ logouts: Arc<watch::Sender<Logouts>>,4041
) {4142
loop {4243
let event = match changes.recv().await {4344
Ok(SessionChange::TokensRefreshed) => Event::SessionTokensChanged,4445
Ok(SessionChange::UnknownToken(data)) => {4546
let soft_logout = data.soft_logout.then_some(device_id);46
- let _ = events47
- .send(Event::SessionAccessRejected {48
- soft_logout: soft_logout.clone(),49
- })50
- .await;51
- let _ = stop.send(match soft_logout {52
- Some(device_id) => Termination::SoftLoggedOut { device_id },53
- None => Termination::AccessRevoked,47
+ let mut held = false;48
+ // our own logout revokes the token, so a rejection meanwhile is its doing49
+ logouts.send_if_modified(|logouts| {50
+ held = logouts.in_flight > 0;51
+ if held {52
+ logouts.rejected = Some(soft_logout.clone());53
+ }54
+ held5455
});56
+ if !held {57
+ reject_session(&events, &stop, soft_logout).await;58
+ }5559
return;5660
}5761
// a missed refresh costs one needless save, a missed rejection comes round again@@ -64,6 +68,22 @@ pub(super) async fn session(
6468
}6569
}6670
71
+pub(super) async fn reject_session(72
+ events: &EventSender,73
+ stop: &mpsc::UnboundedSender<Termination>,74
+ soft_logout: Option<OwnedDeviceId>,75
+) {76
+ let _ = events77
+ .send(Event::SessionAccessRejected {78
+ soft_logout: soft_logout.clone(),79
+ })80
+ .await;81
+ let _ = stop.send(match soft_logout {82
+ Some(device_id) => Termination::SoftLoggedOut { device_id },83
+ None => Termination::AccessRevoked,84
+ });85
+}86
+6787
/// `states` must be taken before sync starts, a late subscriber never hears the transition it missed6888
pub(super) async fn connection(6989
mut states: impl futures_util::Stream<Item = SyncState> + Unpin,