@@ -1306,17 +1306,14 @@ impl Replicator {
13061306 }
13071307
13081308 // Returns the number of pages stored in the local WAL file, or 0, if there aren't any.
1309- async fn get_local_wal_page_count ( & mut self ) -> u32 {
1310- match WalFileReader :: open ( & format ! ( "{}-wal" , & self . db_path) ) . await {
1311- Ok ( None ) => 0 ,
1312- Ok ( Some ( wal) ) => {
1309+ async fn get_local_wal_page_count ( & mut self ) -> Result < u32 > {
1310+ match WalFileReader :: open ( & format ! ( "{}-wal" , & self . db_path) ) . await ? {
1311+ None => Ok ( 0 ) ,
1312+ Some ( wal) => {
13131313 let page_size = wal. page_size ( ) ;
1314- if self . set_page_size ( page_size as usize ) . is_err ( ) {
1315- return 0 ;
1316- }
1317- wal. frame_count ( ) . await
1314+ self . set_page_size ( page_size as usize ) ?;
1315+ Ok ( wal. frame_count ( ) . await )
13181316 }
1319- Err ( _) => 0 ,
13201317 }
13211318 }
13221319
@@ -1381,8 +1378,10 @@ impl Replicator {
13811378 . await
13821379 {
13831380 Ok ( result) => {
1381+ // Move any existing local state aside instead of overwriting or
1382+ // deleting it, so a wrong restore decision stays recoverable.
1383+ self . preserve_local_state_aside ( ) . await ;
13841384 tokio:: fs:: rename ( & restore_path, & self . db_path ) . await ?;
1385- let _ = self . remove_wal_files ( ) . await ; // best effort, WAL files may not exists
13861385
13871386 let elapsed = Instant :: now ( ) - start_ts;
13881387 tracing:: info!( "Finished database restoration in {:?}" , elapsed) ;
@@ -1496,18 +1495,29 @@ impl Replicator {
14961495 last_consistent_frame : u32 ,
14971496 ) -> Result < Option < RestoreAction > > {
14981497 // Check if the database needs to be restored by inspecting the database
1499- // change counter and the WAL size.
1500- let local_counter = self . read_change_counter ( ) . unwrap_or ( [ 0u8 ; 4 ] ) ;
1498+ // change counter and the WAL size. A read error must never be taken as
1499+ // an empty database: restoring over unreadable-but-present local state
1500+ // destroys acknowledged writes.
1501+ let local_counter = tokio:: task:: block_in_place ( || self . read_change_counter ( ) ) ?;
15011502 if local_counter != [ 0u8 ; 4 ] && local_counter != [ 0 , 0 , 0 , 1 ] {
15021503 // if a non-empty database file exists always treat it as new and more up to date,
15031504 // skipping the restoration process and calling for a new generation to be made
15041505 return Ok ( Some ( RestoreAction :: SnapshotMainDbFile ) ) ;
15051506 }
15061507
1508+ let wal_pages = self . get_local_wal_page_count ( ) . await ?;
1509+ if wal_pages > 0 {
1510+ // A fresh-looking main file with committed WAL frames is local data
1511+ // (the change counter only moves on checkpoint), not an empty db.
1512+ tracing:: info!(
1513+ "Local WAL holds {} committed pages; treating local state as authoritative" ,
1514+ wal_pages
1515+ ) ;
1516+ return Ok ( Some ( RestoreAction :: SnapshotMainDbFile ) ) ;
1517+ }
1518+
15071519 let remote_counter = self . get_remote_change_counter ( & generation) . await ?;
15081520 tracing:: debug!( "Counters: l={:?}, r={:?}" , local_counter, remote_counter) ;
1509-
1510- let wal_pages = self . get_local_wal_page_count ( ) . await ;
15111521 // We impersonate as a given generation, since we're comparing against local backup at that
15121522 // generation. This is used later in [Self::new_generation] to create a dependency between
15131523 // this generation and a new one.
@@ -1745,11 +1755,25 @@ impl Replicator {
17451755 Ok ( applied_wal_frame)
17461756 }
17471757
1748- async fn remove_wal_files ( & self ) -> Result < ( ) > {
1749- tracing:: debug!( "Overwriting any existing WAL file: {}-wal" , & self . db_path) ;
1750- tokio:: fs:: remove_file ( & format ! ( "{}-wal" , & self . db_path) ) . await ?;
1751- tokio:: fs:: remove_file ( & format ! ( "{}-shm" , & self . db_path) ) . await ?;
1752- Ok ( ( ) )
1758+ /// Move the local database, WAL, and shm files to `.pre-restore` siblings
1759+ /// (replacing any previous set) before a restore overwrites them. Best
1760+ /// effort: a rename failure is logged, never fatal, and never leaves the
1761+ /// restore blocked.
1762+ async fn preserve_local_state_aside ( & self ) {
1763+ for suffix in [ "" , "-wal" , "-shm" ] {
1764+ let src = format ! ( "{}{}" , & self . db_path, suffix) ;
1765+ if matches ! ( tokio:: fs:: try_exists( & src) . await , Ok ( true ) ) {
1766+ let dst = format ! ( "{}.pre-restore{}" , & self . db_path, suffix) ;
1767+ match tokio:: fs:: rename ( & src, & dst) . await {
1768+ Ok ( ( ) ) => {
1769+ tracing:: warn!( "preserved local {} as {} before restore" , src, dst)
1770+ }
1771+ Err ( e) => {
1772+ tracing:: error!( "could not preserve {} before restore: {}" , src, e)
1773+ }
1774+ }
1775+ }
1776+ }
17531777 }
17541778
17551779 pub async fn copy ( & mut self , generation : Option < Uuid > , to_dir : String ) -> Result < ( ) > {
0 commit comments