@@ -64,6 +64,8 @@ pub enum SyncError {
64
64
PullDb ( StatusCode , String ) ,
65
65
#[ error( "server returned a lower generation than local: local={0}, remote={1}" ) ]
66
66
InvalidLocalGeneration ( u32 , u32 ) ,
67
+ #[ error( "invalid local state: {0}" ) ]
68
+ InvalidLocalState ( String ) ,
67
69
}
68
70
69
71
impl SyncError {
@@ -509,27 +511,63 @@ impl SyncContext {
509
511
}
510
512
511
513
async fn sync_db_if_needed ( & mut self , generation : u32 ) -> Result < ( ) > {
512
- // we will get the export file only if the remote generation is different from the one we have
513
- if generation == self . durable_generation {
514
- return Ok ( ( ) ) ;
515
- }
516
514
// somehow we are ahead of the remote in generations. following should not happen because
517
515
// we checkpoint only if the remote server tells us to do so.
518
516
if self . durable_generation > generation {
519
517
tracing:: error!(
520
- "server returned a lower generation than what we have: sent ={}, got ={}" ,
518
+ "server returned a lower generation than what we have: local ={}, remote ={}" ,
521
519
self . durable_generation,
522
520
generation
523
521
) ;
524
522
return Err (
525
523
SyncError :: InvalidLocalGeneration ( self . durable_generation , generation) . into ( ) ,
526
524
) ;
527
525
}
528
- tracing:: debug!(
529
- "syncing db file from remote server, generation={}" ,
530
- generation
531
- ) ;
532
- self . sync_db ( generation) . await
526
+ // we use the following heuristic to determine if we need to sync the db file
527
+ // 1. if no db file or the metadata file exists, then user is starting from scratch
528
+ // and we will do the sync
529
+ // 2. if the db file exists, but the metadata file does not exist (or other way around),
530
+ // then local db is in an incorrect state. we stop and return with an error
531
+ // 3. if the db file exists and the metadata file exists, then we don't need to do the
532
+ // sync
533
+ let metadata_exists = check_if_file_exists ( & format ! ( "{}-info" , self . db_path) ) ?;
534
+ let db_file_exists = check_if_file_exists ( & self . db_path ) ?;
535
+ match ( metadata_exists, db_file_exists) {
536
+ ( false , false ) => {
537
+ // neither the db file nor the metadata file exists, lets bootstrap from remote
538
+ tracing:: debug!(
539
+ "syncing db file from remote server, generation={}" ,
540
+ generation
541
+ ) ;
542
+ self . sync_db ( generation) . await
543
+ }
544
+ ( false , true ) => {
545
+ // kinda inconsistent state: DB exists but metadata missing
546
+ // however, this generally not an issue. For a fresh db, a user might do writes
547
+ // locally and then try to do sync later. So in this case, we will not
548
+ // bootstrap the db file and let the user proceed. If it is not a fresh db, the
549
+ // push will fail anyways later.
550
+ // if metadata file does not exist, then generation should be zero
551
+ assert_eq ! ( self . durable_generation, 0 ) ;
552
+ // lets initialise it to first generation
553
+ self . durable_generation = 1 ;
554
+ Ok ( ( ) )
555
+ }
556
+ ( true , false ) => {
557
+ // inconsistent state: Metadata exists but DB missing
558
+ tracing:: error!(
559
+ "local state is incorrect, metadata file exists but db file does not"
560
+ ) ;
561
+ Err ( SyncError :: InvalidLocalState (
562
+ "metadata file exists but db file does not" . to_string ( ) ,
563
+ )
564
+ . into ( ) )
565
+ }
566
+ ( true , true ) => {
567
+ // both files exists, no need to sync
568
+ Ok ( ( ) )
569
+ }
570
+ }
533
571
}
534
572
535
573
/// sync_db will download the db file from the remote server and replace the local file.
@@ -831,3 +869,9 @@ async fn try_pull(
831
869
} )
832
870
}
833
871
}
872
+
873
+ fn check_if_file_exists ( path : & str ) -> core:: result:: Result < bool , SyncError > {
874
+ Path :: new ( & path)
875
+ . try_exists ( )
876
+ . map_err ( SyncError :: io ( "metadata file exists" ) )
877
+ }
0 commit comments