Skip to content

Commit 8f7d444

Browse files
authored
libsql: Fix bootstrap error handling (#2148)
This fixes the following issues: - If we fail to boostrap at database open, return an error instead of allowing local files to be in inconsistent state. Note that we ignored boostrap errors to avoid probing the remote for generation when offline, but that I fixed now in a different way. - If we have a database file, but no metadata, return an error instead of allowing inconsistent state.
2 parents 020c070 + 6c86667 commit 8f7d444

File tree

2 files changed

+23
-25
lines changed

2 files changed

+23
-25
lines changed

libsql/src/database.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -691,17 +691,16 @@ impl Database {
691691
};
692692
use tokio::sync::Mutex;
693693

694-
let _ = tokio::task::block_in_place(move || {
694+
tokio::task::block_in_place(move || {
695695
let rt = tokio::runtime::Builder::new_current_thread()
696696
.enable_all()
697697
.build()
698698
.unwrap();
699699
rt.block_on(async {
700-
// we will ignore if any errors occurred during the bootstrapping the db,
701-
// because the client could be offline when trying to connect.
702-
let _ = db.bootstrap_db().await;
700+
db.bootstrap_db().await?;
701+
Ok::<(), crate::Error>(())
703702
})
704-
});
703+
})?;
705704

706705
let local = db.connect()?;
707706

libsql/src/sync.rs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,14 @@ impl SyncContext {
621621
Ok(info)
622622
}
623623

624-
async fn sync_db_if_needed(&mut self, generation: u32) -> Result<()> {
624+
async fn sync_db_if_needed(&mut self) -> Result<()> {
625+
let db_file_exists = check_if_file_exists(&self.db_path)?;
626+
let metadata_exists = check_if_file_exists(&format!("{}-info", self.db_path))?;
627+
if db_file_exists && metadata_exists {
628+
return Ok(());
629+
}
630+
let info = self.get_remote_info().await?;
631+
let generation = info.current_generation;
625632
// somehow we are ahead of the remote in generations. following should not happen because
626633
// we checkpoint only if the remote server tells us to do so.
627634
if self.durable_generation > generation {
@@ -641,8 +648,6 @@ impl SyncContext {
641648
// then local db is in an incorrect state. we stop and return with an error
642649
// 3. if the db file exists and the metadata file exists, then we don't need to do the
643650
// sync
644-
let metadata_exists = check_if_file_exists(&format!("{}-info", self.db_path))?;
645-
let db_file_exists = check_if_file_exists(&self.db_path)?;
646651
match (metadata_exists, db_file_exists) {
647652
(false, false) => {
648653
// neither the db file nor the metadata file exists, lets bootstrap from remote
@@ -653,16 +658,14 @@ impl SyncContext {
653658
self.sync_db(generation).await
654659
}
655660
(false, true) => {
656-
// kinda inconsistent state: DB exists but metadata missing
657-
// however, this generally not an issue. For a fresh db, a user might do writes
658-
// locally and then try to do sync later. So in this case, we will not
659-
// bootstrap the db file and let the user proceed. If it is not a fresh db, the
660-
// push will fail anyways later.
661-
// if metadata file does not exist, then generation should be zero
662-
assert_eq!(self.durable_generation, 0);
663-
// lets initialise it to first generation
664-
self.durable_generation = 1;
665-
Ok(())
661+
// inconsistent state: DB exists but metadata missing
662+
tracing::error!(
663+
"local state is incorrect, db file exists but metadata file does not"
664+
);
665+
Err(SyncError::InvalidLocalState(
666+
"db file exists but metadata file does not".to_string(),
667+
)
668+
.into())
666669
}
667670
(true, false) => {
668671
// inconsistent state: Metadata exists but DB missing
@@ -675,8 +678,8 @@ impl SyncContext {
675678
.into())
676679
}
677680
(true, true) => {
678-
// both files exists, no need to sync
679-
Ok(())
681+
// We already handled this case earlier in the function.
682+
unreachable!();
680683
}
681684
}
682685
}
@@ -820,11 +823,7 @@ pub async fn bootstrap_db(sync_ctx: &mut SyncContext) -> Result<()> {
820823
// we need to do this when we notice a large gap in generations, when bootstrapping is cheaper
821824
// than pulling each frame
822825
if !sync_ctx.initial_server_sync {
823-
// sync is being called first time. so we will call remote, get the generation information
824-
// if we are lagging behind, then we will call the export API and get to the latest
825-
// generation directly.
826-
let info = sync_ctx.get_remote_info().await?;
827-
sync_ctx.sync_db_if_needed(info.current_generation).await?;
826+
sync_ctx.sync_db_if_needed().await?;
828827
// when sync_ctx is initialised, we set durable_generation to 0. however, once
829828
// sync_db is called, it should be > 0.
830829
assert!(sync_ctx.durable_generation > 0, "generation should be > 0");

0 commit comments

Comments
 (0)