Skip to content

Implement client logout and add logout button #432

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 80 commits into
base: main
Choose a base branch
from

Conversation

TigerInYourDream
Copy link

@TigerInYourDream TigerInYourDream commented Mar 11, 2025

Closes #277. Replaces #293 and #494.


Based on PR #293, I found an error message during logout:
2024-12-24T07:58:55.514036Z ERROR matrix_sdk_ui::sync_service: Error while processing encryption in sync service: Something wrong happened in sliding sync: the server returned an error: [401 / M_UNKNOWN_TOKEN] Invalid access token passed.
Root cause:
The error is related to SYNC_SERVICE handling during logout process.

Changes:

  • Updated SYNC_SERVICE handling during logout
  • Tested the fix with Google SSO login/logout flow

@kevinaboos
Copy link
Member

Thanks Alvin. I'm a bit confused -- is this a replacement for #293, or something that should be merged in after #293 as an addition to it?

Also, is it ready for review or shall I wait for something else?

@TigerInYourDream
Copy link
Author

Thanks Alvin. I'm a bit confused -- is this a replacement for #293, or something that should be merged in after #293 as an addition to it?

Also, is it ready for review or shall I wait for something else?

Due to the age of PR #293 and substantial changes in the codebase, resolving merge conflicts became quite challenging. I've contacted the original author, Guocork, who also suggested creating a new PR. Therefore, I've created a fresh PR implementing the same functionality with updated code and comprehensive testing. Could you please review this new implementation?

Copy link
Member

@kevinaboos kevinaboos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Alvin, great start on this feature!

I left some comments below. In general, make sure to name things carefully --- try to give variables and types very specific names that cannot be misinterpreted. (I know naming is hard, so it'll take some time to get that right. No worries.)

@kevinaboos kevinaboos added the waiting-on-author This issue is waiting on the original author for a response label Mar 12, 2025
@TigerInYourDream
Copy link
Author

Thanks Alvin, great start on this feature!

I left some comments below. In general, make sure to name things carefully --- try to give variables and types very specific names that cannot be misinterpreted. (I know naming is hard, so it'll take some time to get that right. No worries.)

Thank you for the review. I will try to improve the naming conventions and continue working on it.

@TigerInYourDream TigerInYourDream marked this pull request as draft March 13, 2025 04:50
@TigerInYourDream TigerInYourDream marked this pull request as ready for review March 18, 2025 07:24
@alanpoon
Copy link
Contributor

alanpoon commented Mar 18, 2025

  1. Suggest adding a modal for logout confirmation
  2. Perhaps delete the "latest_user_id.txt" in the app data directory
  3. After logout, restarting the app will see a prompt saying fail to "Could not restore previous user session"
    Screenshot 2025-03-18 at 3 51 49 PM

@TigerInYourDream
Copy link
Author

  1. Suggest adding a modal for logout confirmation
  2. Perhaps delete the "latest_user_id.txt" in the app data directory
  3. After logout, restarting the app will see a prompt saying fail to "Could not restore previous user session"
    Screenshot 2025-03-18 at 3 51 49 PM

Thanks for the review.

@TigerInYourDream TigerInYourDream changed the title fix pr #293 about logout logout feature Mar 21, 2025
@kevinaboos
Copy link
Member

kevinaboos commented Jun 27, 2025

Relogin does not restore the dock state, Do you want to fix it in this PR or in another PR?

As I said on the Robrix matrix channel, I don't think re-logging in should restore the dock state by default. Generally, when logging out, most users would expect that the app be "reset" to its default state.

However, in the future, we could show a prompt after log-in asking the user if they want to restore their previous dock/UI layout.

@ZhangHanDong ZhangHanDong added waiting-on-review This issue is waiting to be reviewed waiting-on-author This issue is waiting on the original author for a response and removed waiting-on-author This issue is waiting on the original author for a response waiting-on-review This issue is waiting to be reviewed labels Jul 14, 2025
@ZhangHanDong
Copy link
Contributor

ZhangHanDong commented Jul 15, 2025

Currently, the logout function still logs panic messages, but these do not cause the robrix program to crash.

thread 'tokio-runtime-worker' panicked at /Users/zhangalex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/deadpool-runtime-0.1.4/src/lib.rs:77:58:
`JoinError` reason is not a panic.: JoinError::Cancelled(Id(18764))

This is because the matrix SDK does not implement a graceful shutdown mechanism for deadpool-runtime. As a result, after the main loop of robrix’s tokio runtime exits, deadpool-runtime continues to run. Eventually, when robrix is terminated with ctrl-c, crash messages will appear in the logs:

thread 'main' panicked at /Users/zhangalex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/deadpool-runtime-0.1.4/src/lib.rs:101:22:
there is no reactor running, must be called from the context of a Tokio 1.x runtime
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

thread 'main' panicked at /Users/zhangalex/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/deadpool-runtime-0.1.4/src/lib.rs:101:22:
there is no reactor running, must be called from the context of a Tokio 1.x runtime
stack backtrace:
   0:        0x1041bef7c - std::backtrace_rs::backtrace::libunwind::trace::hd09570c029a6744a
   ...

@kevinaboos Do we need to report this issue in the matrix repository?

update: After the refactoring, these exceptions should all be resolved, so there’s no need to report them upstream.

@ZhangHanDong ZhangHanDong requested a review from kevinaboos July 18, 2025 04:48
@ZhangHanDong ZhangHanDong added waiting-on-review This issue is waiting to be reviewed and removed waiting-on-author This issue is waiting on the original author for a response labels Jul 18, 2025
Copy link
Member

@kevinaboos kevinaboos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, the code looks quite nice, especially the detailed comments and structure in the logout state machine. Nice!

However, I have serious concerns about the approach of leaking a bunch of huge states by mem::forgetting them — that cannot possibly be the best solution. I left some comments about that and other issues, please let me know more about this issue so we can brainstorm an alternative solution.

@@ -128,7 +129,7 @@ live_design! {
// However, the DesktopButton widget doesn't support drawing a background color yet,
// so these colors are the colors of the icon itself, not the background highlight.
// When it supports that, we will keep the icon color always black,
// and change the background color instead based on the above colors.
// and change the background color instead bssed on instead e colors
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strange typo here. Please undo.

Comment on lines +494 to +519
// Immediately take and leak all resources to prevent any destructors from running
// This is a controlled leak at shutdown to avoid the deadpool panic

// Take the runtime first and leak it
if let Some(runtime) = TOKIO_RUNTIME.lock().unwrap().take() {
std::mem::forget(runtime);
log!("Leaked tokio runtime to prevent destructor");
}

// Take and leak the client
if let Some(client) = CLIENT.lock().unwrap().take() {
std::mem::forget(client);
log!("Leaked client to prevent destructor");
}

// Take and leak the sync service
if let Some(sync_service) = SYNC_SERVICE.lock().unwrap().take() {
std::mem::forget(sync_service);
log!("Leaked sync service to prevent destructor");
}

// Take and leak the request sender
if let Some(sender) = REQUEST_SENDER.lock().unwrap().take() {
std::mem::forget(sender);
log!("Leaked request sender to prevent destructor");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but leaking a bunch of states is not a solution that I will accept.

What's the root cause here? Alex mentioned something about a Matrix SDK issue regarding deadpool, but I'm not really sure what that means. Please elaborate why this is needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevinaboos This intentional memory leak occurs because, when canceling asynchronous tasks during logout, the deadpool in the matrix SDK cannot be cancelled. As a result, when the robrix application is closed, a panic occurs, causing a crash and an exception dialog that affects the user experience. However, this intentional leak only happens during the shutdown process of robrix. In other words, as long as the user does not close the robrix program, the intentional leak will not occur.

Therefore, this intentional leak is a last-resort measure to preserve user experience when there is no other solution (at least, no proper way to destruct deadpool has been found so far). I think that after the program exits, an intentional leak shouldn’t be a problem, right? After all, the memory should be reclaimed by the system once the program is closed.

Comment on lines +350 to +352

}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lots of whitespace

Suggested change
}
}

self.label(id!(message)).set_text(cx, message);
}

fn reset_state(&mut self, cx: &mut Cx) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

due to a Makepad bug, in this function, you also need to call reset_hover() on every button in the modal.

Comment on lines +269 to +276
/// Clears the user profile cache.
/// This is useful during shutdown to prevent thread-local destructors
/// from running after the tokio runtime has been shut down.
pub fn clear_cache() {
USER_PROFILE_CACHE.with_borrow_mut(|cache| {
cache.clear();
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make any sense — can you explain?

  1. Thread-local destructors are good, we want them to run.
  2. Clearing the cache has nothing to do with thread-local destructors.
  3. This cache is only accessed from the main thread, and the main thread is not being exited/killed, so its destructors wouldn't run anyway.

Note that I do agree that we need to clear all caches upon logout, including the USER_PROFILE_CACHE and AVATAR_CACHE (please add that as well). I just don't understand the comments here about thread locals..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to clearing the user profile cache and the avatar cache, you also need to clear the list of ALL_INVITED_ROOMS here:

thread_local! {
/// The list of all invited rooms, which is only tracked here
/// because the backend doesn't need to track any info about them.
///
/// This must only be accessed by the main UI thread.
static ALL_INVITED_ROOMS: Rc<RefCell<HashMap<OwnedRoomId, InvitedRoomInfo>>> = Rc::new(RefCell::new(HashMap::new()));
}

and the set of all timeline states here:

robrix/src/home/room_screen.rs

Lines 2781 to 2788 in 5d5bc20

thread_local! {
/// The global set of all timeline states, one entry per room.
///
/// This is only useful when accessed from the main UI thread.
static TIMELINE_STATES: RefCell<BTreeMap<OwnedRoomId, TimelineUiState>> = const {
RefCell::new(BTreeMap::new())
};
}

Comment on lines -1281 to +1345
static ALL_JOINED_ROOMS: Mutex<BTreeMap<OwnedRoomId, JoinedRoomDetails>> = Mutex::new(BTreeMap::new());
pub(crate) static ALL_JOINED_ROOMS: Mutex<BTreeMap<OwnedRoomId, JoinedRoomDetails>> = Mutex::new(BTreeMap::new());

/// Information about all of the rooms that have been tombstoned.
///
/// The map key is the **NEW** replacement room ID, and the value is the **OLD** tombstoned room ID.
/// This allows us to quickly query if a newly-encountered room is a replacement for an old tombstoned room.
static TOMBSTONED_ROOMS: Mutex<BTreeMap<OwnedRoomId, OwnedRoomId>> = Mutex::new(BTreeMap::new());
pub(crate) static TOMBSTONED_ROOMS: Mutex<BTreeMap<OwnedRoomId, OwnedRoomId>> = Mutex::new(BTreeMap::new());

/// The logged-in Matrix client, which can be freely and cheaply cloned.
static CLIENT: OnceLock<Client> = OnceLock::new();
pub(crate) static CLIENT: Mutex<Option<Client>> = Mutex::new(None);

pub fn get_client() -> Option<Client> {
CLIENT.get().cloned()
CLIENT.lock().unwrap().clone()
}

/// Returns the user ID of the currently logged-in user, if any.
pub fn current_user_id() -> Option<OwnedUserId> {
CLIENT.get().and_then(|c|
CLIENT.lock().unwrap().as_ref().and_then(|c|
c.session_meta().map(|m| m.user_id.clone())
)
}

/// The singleton sync service.
static SYNC_SERVICE: OnceLock<SyncService> = OnceLock::new();
/// sync_service if build from the client, and is used to sync the client with the server.
pub(crate) static SYNC_SERVICE: Mutex<Option<Arc<SyncService>>> = Mutex::new(None);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of these static states should not be public in any way. We only want the sliding sync module's logic to be aware of them and be able to access them.

If you just need a function that clears all of these upon logout, then add a function to this module and call it from your logout logic. There's no reason to make them public if that's all you need.

@kevinaboos kevinaboos added waiting-on-author This issue is waiting on the original author for a response and removed waiting-on-review This issue is waiting to be reviewed labels Jul 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
waiting-on-author This issue is waiting on the original author for a response
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add logout button
7 participants