From d73b4332bf8de700695f4ed6536eceaa87d53913 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Thu, 11 Sep 2025 02:33:01 +0000 Subject: [PATCH] Fix deletion of local leave state to allow sync for clients. Signed-off-by: Jason Volk --- src/admin/room/commands.rs | 4 +-- src/admin/room/mod.rs | 3 ++ src/admin/room/moderation.rs | 4 +-- src/admin/user/commands.rs | 2 +- src/api/client/membership/leave.rs | 2 +- src/service/deactivate/mod.rs | 2 +- src/service/membership/leave.rs | 12 ++++++-- src/service/rooms/delete/mod.rs | 46 +++++++--------------------- src/service/rooms/state_cache/mod.rs | 5 ++- 9 files changed, 34 insertions(+), 46 deletions(-) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 6ba1d9fa..c85947a5 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -67,7 +67,7 @@ pub(super) async fn exists(&self, room_id: OwnedRoomId) -> Result { } #[admin_command] -pub(super) async fn delete_room(&self, room_id: OwnedRoomId) -> Result { +pub(super) async fn delete_room(&self, room_id: OwnedRoomId, force: bool) -> Result { if self.services.admin.is_admin_room(&room_id).await { return Err!("Cannot delete admin room"); } @@ -76,7 +76,7 @@ pub(super) async fn delete_room(&self, room_id: OwnedRoomId) -> Result { self.services .delete - .delete_room(&room_id, state_lock) + .delete_room(&room_id, force, state_lock) .await?; self.write_str("Successfully deleted the room from our database.") diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index c3980fc2..8bdfaa52 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -60,5 +60,8 @@ pub(super) enum RoomCommand { /// - Delete room DeleteRoom { room_id: OwnedRoomId, + + #[arg(short, long)] + force: bool, }, } diff --git a/src/admin/room/moderation.rs b/src/admin/room/moderation.rs index c6e8bfbb..0a68868a 100644 --- a/src/admin/room/moderation.rs +++ b/src/admin/room/moderation.rs @@ -156,7 +156,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result { if let Err(e) = self .services .membership - .leave(user_id, &room_id, None, &state_lock) + .leave(user_id, &room_id, None, false, &state_lock) .boxed() .await { @@ -331,7 +331,7 @@ async fn ban_list_of_rooms(&self) -> Result { if let Err(e) = self .services .membership - .leave(user_id, &room_id, None, &state_lock) + .leave(user_id, &room_id, None, false, &state_lock) .boxed() .await { diff --git a/src/admin/user/commands.rs b/src/admin/user/commands.rs index 18da60b7..b5b9261b 100644 --- a/src/admin/user/commands.rs +++ b/src/admin/user/commands.rs @@ -665,7 +665,7 @@ pub(super) async fn force_leave_room( self.services .membership - .leave(&user_id, &room_id, None, &state_lock) + .leave(&user_id, &room_id, None, false, &state_lock) .boxed() .await?; diff --git a/src/api/client/membership/leave.rs b/src/api/client/membership/leave.rs index d5e72bea..0d2974a4 100644 --- a/src/api/client/membership/leave.rs +++ b/src/api/client/membership/leave.rs @@ -18,7 +18,7 @@ pub(crate) async fn leave_room_route( services .membership - .leave(body.sender_user(), &body.room_id, body.reason.clone(), &state_lock) + .leave(body.sender_user(), &body.room_id, body.reason.clone(), false, &state_lock) .boxed() .await?; diff --git a/src/service/deactivate/mod.rs b/src/service/deactivate/mod.rs index ba4a5dfa..43c5fdeb 100644 --- a/src/service/deactivate/mod.rs +++ b/src/service/deactivate/mod.rs @@ -145,7 +145,7 @@ impl Service { if let Err(e) = self .services .membership - .leave(user_id, &room_id, None, &state_lock) + .leave(user_id, &room_id, None, false, &state_lock) .boxed() .await { diff --git a/src/service/membership/leave.rs b/src/service/membership/leave.rs index 19c5d43b..db82b9fc 100644 --- a/src/service/membership/leave.rs +++ b/src/service/membership/leave.rs @@ -30,6 +30,7 @@ pub async fn leave( user_id: &UserId, room_id: &RoomId, reason: Option, + remote_leave_now: bool, state_lock: &RoomMutexGuard, ) -> Result { let default_member_content = RoomMemberEventContent { @@ -79,7 +80,7 @@ pub async fn leave( .eq(&false); // Ask a remote server if we don't have this room and are not knocking on it - if dont_have_room.and(not_knocked).await { + if remote_leave_now || dont_have_room.and(not_knocked).await { if let Err(e) = self.remote_leave(user_id, room_id).boxed().await { warn!(%user_id, "Failed to leave room {room_id} remotely: {e}"); // Don't tell the client about this error @@ -167,7 +168,7 @@ pub async fn leave( #[implement(Service)] #[tracing::instrument(name = "remote", level = "debug", skip_all)] -pub async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result { +async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result { let mut make_leave_response_and_server = Err!(BadServerResponse("No remote server available to assist in leaving {room_id}.")); @@ -175,6 +176,7 @@ pub async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result { .services .state_cache .servers_invite_via(room_id) + .chain(self.services.state_cache.room_servers(room_id)) .map(ToOwned::to_owned) .collect() .await; @@ -221,13 +223,17 @@ pub async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result { }, } + servers.insert(user_id.server_name().to_owned()); if let Some(room_id_server_name) = room_id.server_name() { servers.insert(room_id_server_name.to_owned()); } debug_info!("servers in remote_leave_room: {servers:?}"); - for remote_server in servers { + for remote_server in servers + .into_iter() + .filter(|server| !self.services.globals.server_is_ours(server)) + { let make_leave_response = self .services .sending diff --git a/src/service/rooms/delete/mod.rs b/src/service/rooms/delete/mod.rs index 80067ca8..8af5f2d1 100644 --- a/src/service/rooms/delete/mod.rs +++ b/src/service/rooms/delete/mod.rs @@ -1,8 +1,7 @@ -use std::{sync::Arc, time::Duration}; +use std::sync::Arc; use futures::{FutureExt, StreamExt, pin_mut}; use ruma::RoomId; -use tokio::time::sleep; use tuwunel_core::{ Result, debug, result::LogErr, @@ -54,30 +53,20 @@ impl Service { debug!(?room_id, "Preparing to delete room..."); - // Some arbitrary delay has to account for the leave event being synced to the - // client or they'll never be updated on their leave. This can be removed once - // a tombstone solution is implemented instead. - sleep(Duration::from_millis(2500)).await; - self.services .delete - .delete_room(room_id, state_lock) + .delete_room(room_id, false, state_lock) .boxed() .await .expect("unhandled error during room deletion"); } - pub async fn delete_room(&self, room_id: &RoomId, state_lock: RoomMutexGuard) -> Result { - // ban the room locally so new users cannot join while we're in the process of - // deleting it - debug!("Banning room {room_id} prior to deletion."); - self.services.metadata.ban_room(room_id); - - // This might have to be dropped here to prevent deadlock, but the goal should - // be to hold it all the way through. For now the room is banned under lock at - // least. - drop(state_lock); - + pub async fn delete_room( + &self, + room_id: &RoomId, + force: bool, + state_lock: RoomMutexGuard, + ) -> Result { debug!("Making all users leave the room {room_id} and forgetting it"); let mut users = self .services @@ -95,18 +84,14 @@ impl Service { if let Err(e) = self .services .membership - .remote_leave(user_id, room_id) + .leave(user_id, room_id, Some("Room Deleted".into()), true, &state_lock) + .boxed() .await { warn!("Failed to leave room: {e}"); } - - self.services.state_cache.forget(room_id, user_id); } - debug!("Disabling incoming federation on room {room_id}"); - self.services.metadata.disable_room(room_id); - debug!("Deleting all our room aliases for the room"); self.services .alias @@ -159,7 +144,7 @@ impl Service { debug!("Deleting all the room's member counts"); self.services .state_cache - .delete_room_join_counts(room_id) + .delete_room_join_counts(room_id, force) .await .log_err() .ok(); @@ -174,9 +159,6 @@ impl Service { debug!("Final stages of deleting the room"); - debug!("Obtaining a mutex state lock for safety and future database operations"); - let state_lock = self.services.state.mutex.lock(room_id).await; - debug!("Deleting room state hash from our database"); self.services .state @@ -201,12 +183,6 @@ impl Service { .log_err() .ok(); - // TODO: add option to keep a room banned (`--block` or `--ban`) - self.services.metadata.enable_room(room_id); - self.services.metadata.unban_room(room_id); - - drop(state_lock); - debug!("Successfully deleted room {room_id} from our database"); Ok(()) } diff --git a/src/service/rooms/state_cache/mod.rs b/src/service/rooms/state_cache/mod.rs index 21b08700..939f5dea 100644 --- a/src/service/rooms/state_cache/mod.rs +++ b/src/service/rooms/state_cache/mod.rs @@ -577,7 +577,7 @@ pub async fn is_left(&self, user_id: &UserId, room_id: &RoomId) -> bool { #[implement(Service)] #[tracing::instrument(skip(self), level = "trace")] -pub async fn delete_room_join_counts(&self, room_id: &RoomId) -> Result { +pub async fn delete_room_join_counts(&self, room_id: &RoomId, force: bool) -> Result { let prefix = (room_id, Interfix); self.db.roomid_knockedcount.remove(room_id); @@ -648,6 +648,9 @@ pub async fn delete_room_join_counts(&self, room_id: &RoomId) -> Result { .roomuserid_leftcount .keys_prefix(&prefix) .ignore_err() + .ready_filter(|(_, user_id): &(&RoomId, &UserId)| { + force || !self.services.globals.user_is_local(user_id) + }) .ready_for_each(|key: (&RoomId, &UserId)| { trace!("Removing key: {key:?}"); self.db.roomuserid_leftcount.del(key);