Skip to content

Commit

Permalink
Auto-recover from "got state, but no session" errors
Browse files Browse the repository at this point in the history
The easiest way to reproduce the problem is to try and `init()` with an
incorrect username/password.

Initialization would make some progress (and prepare some state files in
the DB directory), but would ultimately fail. With no session file
stored, these state files cannot be made use of later on.

We now have auto-recovery logic which detects this condition and purges
the database state files, so that a new session can be created cleanly.
  • Loading branch information
spantaleev committed Sep 13, 2024
1 parent afdc4fe commit f339fc8
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 1 deletion.
18 changes: 18 additions & 0 deletions src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ pub enum InitError {
#[error("Error restoring existing session: {0}")]
RestoreSession(RestoreSessionError),

#[error("Error purging existing database: {0}")]
PurgeDatabase(std::io::Error),

#[error("Whoami sanity check failed due to an invalid access token. You may need to delete all persisted data (session and database) and start fresh")]
WhoAmISanityCheckFailed,

Expand Down Expand Up @@ -115,6 +118,21 @@ pub async fn init(init_config: &InitConfig) -> Result<MatrixLink, InitError> {
});

perform_whoami_sanity_check(&client).await?;
} else {
// No session file. Let's make sure the database directory is empty too, so we can start a new session cleanly.

if persistence_manager.has_existing_db_state_file() {
tracing::warn!(
"Found an existing database state file ({}), but no session file ({}). This may happen when a previous initialization attempt failed mid-way or if the session file was deleted subsequently. The only way to recover is to start fresh. Doing that now..",
persistence_manager.db_state_file_path().to_string_lossy(),
persistence_manager.session_file_path().to_string_lossy(),
);

persistence_manager.purge_database()
.map_err(InitError::PurgeDatabase)?;

tracing::info!("The old database has been purged successfully");
}
}

let client_state = if let Some(client_state) = client_state {
Expand Down
37 changes: 36 additions & 1 deletion src/persistence.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use tokio::fs;

use thiserror::Error;
Expand Down Expand Up @@ -41,8 +43,41 @@ impl Manager {
}
}

pub(crate) fn session_file_path(&self) -> PathBuf {
self.config.session_file_path.clone()
}

pub(crate) fn db_state_file_path(&self) -> PathBuf {
self.config.db_dir_path.join("matrix-sdk-state.sqlite3")
}

pub(crate) fn has_existing_session(&self) -> bool {
self.config.session_file_path.exists()
self.session_file_path().exists()
}

pub(crate) fn has_existing_db_state_file(&self) -> bool {
self.db_state_file_path().exists()
}

pub(crate) fn purge_database(&self) -> Result<(), std::io::Error> {
let base_path = self.config.db_dir_path.clone();

for entry in std::fs::read_dir(base_path)? {
let entry = entry?;
let path = entry.path();
if !path.is_file() {
continue;
}

// Out of precaution, we'll only be deleting *.sqlite3 files
if !path.extension().map_or(false, |ext| ext == "sqlite3") {
continue;
}

std::fs::remove_file(path)?;
}

Ok(())
}

pub(crate) async fn read_full_session(&self) -> Result<FullSession, SessionPersistenceError> {
Expand Down

0 comments on commit f339fc8

Please sign in to comment.