@@ -61,16 +61,23 @@ impl SyncError {
61
61
}
62
62
}
63
63
64
+ pub enum PullResult {
65
+ /// A frame was successfully pulled.
66
+ Frame ( Bytes ) ,
67
+ /// We've reached the end of the generation.
68
+ EndOfGeneration { max_generation : u32 } ,
69
+ }
70
+
64
71
pub struct SyncContext {
65
72
db_path : String ,
66
73
client : hyper:: Client < ConnectorService , Body > ,
67
74
sync_url : String ,
68
75
auth_token : Option < HeaderValue > ,
69
76
max_retries : usize ,
77
+ /// The current durable generation.
78
+ durable_generation : u32 ,
70
79
/// Represents the max_frame_no from the server.
71
80
durable_frame_num : u32 ,
72
- /// Represents the current checkpoint generation.
73
- generation : u32 ,
74
81
}
75
82
76
83
impl SyncContext {
@@ -96,8 +103,8 @@ impl SyncContext {
96
103
auth_token,
97
104
max_retries : DEFAULT_MAX_RETRIES ,
98
105
client,
106
+ durable_generation : 1 ,
99
107
durable_frame_num : 0 ,
100
- generation : 1 ,
101
108
} ;
102
109
103
110
if let Err ( e) = me. read_metadata ( ) . await {
@@ -115,7 +122,7 @@ impl SyncContext {
115
122
& mut self ,
116
123
generation : u32 ,
117
124
frame_no : u32 ,
118
- ) -> Result < Option < Bytes > > {
125
+ ) -> Result < PullResult > {
119
126
let uri = format ! (
120
127
"{}/sync/{}/{}/{}" ,
121
128
self . sync_url,
@@ -124,13 +131,7 @@ impl SyncContext {
124
131
frame_no + 1
125
132
) ;
126
133
tracing:: debug!( "pulling frame" ) ;
127
- match self . pull_with_retry ( uri, self . max_retries ) . await ? {
128
- Some ( frame) => {
129
- self . durable_frame_num = frame_no;
130
- Ok ( Some ( frame) )
131
- }
132
- None => Ok ( None ) ,
133
- }
134
+ self . pull_with_retry ( uri, self . max_retries ) . await
134
135
}
135
136
136
137
#[ tracing:: instrument( skip( self , frame) ) ]
@@ -149,7 +150,7 @@ impl SyncContext {
149
150
) ;
150
151
tracing:: debug!( "pushing frame" ) ;
151
152
152
- let durable_frame_num = self . push_with_retry ( uri, frame, self . max_retries ) . await ?;
153
+ let ( generation , durable_frame_num) = self . push_with_retry ( uri, frame, self . max_retries ) . await ?;
153
154
154
155
if durable_frame_num > frame_no {
155
156
tracing:: error!(
@@ -178,12 +179,14 @@ impl SyncContext {
178
179
tracing:: debug!( ?durable_frame_num, "frame successfully pushed" ) ;
179
180
180
181
// Update our last known max_frame_no from the server.
182
+ tracing:: debug!( ?generation, ?durable_frame_num, "updating remote generation and durable_frame_num" ) ;
183
+ self . durable_generation = generation;
181
184
self . durable_frame_num = durable_frame_num;
182
185
183
186
Ok ( durable_frame_num)
184
187
}
185
188
186
- async fn push_with_retry ( & self , uri : String , frame : Bytes , max_retries : usize ) -> Result < u32 > {
189
+ async fn push_with_retry ( & self , uri : String , frame : Bytes , max_retries : usize ) -> Result < ( u32 , u32 ) > {
187
190
let mut nr_retries = 0 ;
188
191
loop {
189
192
let mut req = http:: Request :: post ( uri. clone ( ) ) ;
@@ -213,6 +216,14 @@ impl SyncContext {
213
216
let resp = serde_json:: from_slice :: < serde_json:: Value > ( & res_body[ ..] )
214
217
. map_err ( SyncError :: JsonDecode ) ?;
215
218
219
+ let generation = resp
220
+ . get ( "generation" )
221
+ . ok_or_else ( || SyncError :: JsonValue ( resp. clone ( ) ) ) ?;
222
+
223
+ let generation = generation
224
+ . as_u64 ( )
225
+ . ok_or_else ( || SyncError :: JsonValue ( generation. clone ( ) ) ) ?;
226
+
216
227
let max_frame_no = resp
217
228
. get ( "max_frame_no" )
218
229
. ok_or_else ( || SyncError :: JsonValue ( resp. clone ( ) ) ) ?;
@@ -221,7 +232,7 @@ impl SyncContext {
221
232
. as_u64 ( )
222
233
. ok_or_else ( || SyncError :: JsonValue ( max_frame_no. clone ( ) ) ) ?;
223
234
224
- return Ok ( max_frame_no as u32 ) ;
235
+ return Ok ( ( generation as u32 , max_frame_no as u32 ) ) ;
225
236
}
226
237
227
238
// If we've retried too many times or the error is not a server error,
@@ -244,7 +255,7 @@ impl SyncContext {
244
255
}
245
256
}
246
257
247
- async fn pull_with_retry ( & self , uri : String , max_retries : usize ) -> Result < Option < Bytes > > {
258
+ async fn pull_with_retry ( & self , uri : String , max_retries : usize ) -> Result < PullResult > {
248
259
let mut nr_retries = 0 ;
249
260
loop {
250
261
let mut req = http:: Request :: builder ( ) . method ( "GET" ) . uri ( uri. clone ( ) ) ;
@@ -268,10 +279,27 @@ impl SyncContext {
268
279
let frame = hyper:: body:: to_bytes ( res. into_body ( ) )
269
280
. await
270
281
. map_err ( SyncError :: HttpBody ) ?;
271
- return Ok ( Some ( frame) ) ;
282
+ return Ok ( PullResult :: Frame ( frame) ) ;
283
+ }
284
+ if res. status ( ) == StatusCode :: BAD_REQUEST {
285
+ let res_body = hyper:: body:: to_bytes ( res. into_body ( ) )
286
+ . await
287
+ . map_err ( SyncError :: HttpBody ) ?;
288
+
289
+ let resp = serde_json:: from_slice :: < serde_json:: Value > ( & res_body[ ..] )
290
+ . map_err ( SyncError :: JsonDecode ) ?;
291
+
292
+ let generation = resp
293
+ . get ( "generation" )
294
+ . ok_or_else ( || SyncError :: JsonValue ( resp. clone ( ) ) ) ?;
295
+
296
+ let generation = generation
297
+ . as_u64 ( )
298
+ . ok_or_else ( || SyncError :: JsonValue ( generation. clone ( ) ) ) ?;
299
+ return Ok ( PullResult :: EndOfGeneration { max_generation : generation as u32 } ) ;
272
300
}
273
301
if res. status ( ) == StatusCode :: BAD_REQUEST {
274
- return Ok ( None ) ;
302
+ return Err ( SyncError :: PullFrame ( res . status ( ) , "Bad Request" . to_string ( ) ) . into ( ) ) ;
275
303
}
276
304
// If we've retried too many times or the error is not a server error,
277
305
// return the error.
@@ -293,12 +321,18 @@ impl SyncContext {
293
321
}
294
322
}
295
323
324
+
325
+ pub ( crate ) fn next_generation ( & mut self ) {
326
+ self . durable_generation += 1 ;
327
+ self . durable_frame_num = 0 ;
328
+ }
329
+
296
330
pub ( crate ) fn durable_frame_num ( & self ) -> u32 {
297
331
self . durable_frame_num
298
332
}
299
333
300
- pub ( crate ) fn generation ( & self ) -> u32 {
301
- self . generation
334
+ pub ( crate ) fn durable_generation ( & self ) -> u32 {
335
+ self . durable_generation
302
336
}
303
337
304
338
pub ( crate ) async fn write_metadata ( & mut self ) -> Result < ( ) > {
@@ -308,7 +342,7 @@ impl SyncContext {
308
342
hash : 0 ,
309
343
version : METADATA_VERSION ,
310
344
durable_frame_num : self . durable_frame_num ,
311
- generation : self . generation ,
345
+ generation : self . durable_generation ,
312
346
} ;
313
347
314
348
metadata. set_hash ( ) ;
@@ -350,8 +384,8 @@ impl SyncContext {
350
384
metadata
351
385
) ;
352
386
387
+ self . durable_generation = metadata. generation ;
353
388
self . durable_frame_num = metadata. durable_frame_num ;
354
- self . generation = metadata. generation ;
355
389
356
390
Ok ( ( ) )
357
391
}
@@ -436,10 +470,7 @@ pub async fn sync_offline(
436
470
sync_ctx : & mut SyncContext ,
437
471
conn : & Connection ,
438
472
) -> Result < crate :: database:: Replicated > {
439
- let durable_frame_no = sync_ctx. durable_frame_num ( ) ;
440
- let max_frame_no = conn. wal_frame_count ( ) ;
441
-
442
- if max_frame_no > durable_frame_no {
473
+ if is_ahead_of_remote ( & sync_ctx, & conn) {
443
474
match try_push ( sync_ctx, conn) . await {
444
475
Ok ( rep) => Ok ( rep) ,
445
476
Err ( Error :: Sync ( err) ) => {
@@ -475,6 +506,11 @@ pub async fn sync_offline(
475
506
} )
476
507
}
477
508
509
+ fn is_ahead_of_remote ( sync_ctx : & SyncContext , conn : & Connection ) -> bool {
510
+ let max_local_frame = conn. wal_frame_count ( ) ;
511
+ max_local_frame > sync_ctx. durable_frame_num ( )
512
+ }
513
+
478
514
async fn try_push (
479
515
sync_ctx : & mut SyncContext ,
480
516
conn : & Connection ,
@@ -496,7 +532,7 @@ async fn try_push(
496
532
} ) ;
497
533
}
498
534
499
- let generation = sync_ctx. generation ( ) ; // TODO: Probe from WAL.
535
+ let generation = sync_ctx. durable_generation ( ) ;
500
536
let start_frame_no = sync_ctx. durable_frame_num ( ) + 1 ;
501
537
let end_frame_no = max_frame_no;
502
538
@@ -532,29 +568,60 @@ async fn try_pull(
532
568
sync_ctx : & mut SyncContext ,
533
569
conn : & Connection ,
534
570
) -> Result < crate :: database:: Replicated > {
535
- let generation = sync_ctx. generation ( ) ;
536
- let mut frame_no = sync_ctx. durable_frame_num ( ) + 1 ;
537
-
538
571
let insert_handle = conn. wal_insert_handle ( ) ?;
539
572
573
+ let mut err = None ;
574
+
540
575
loop {
576
+ let generation = sync_ctx. durable_generation ( ) ;
577
+ let frame_no = sync_ctx. durable_frame_num ( ) + 1 ;
541
578
match sync_ctx. pull_one_frame ( generation, frame_no) . await {
542
- Ok ( Some ( frame) ) => {
579
+ Ok ( PullResult :: Frame ( frame) ) => {
543
580
insert_handle. insert ( & frame) ?;
544
- frame_no += 1 ;
581
+ sync_ctx . durable_frame_num = frame_no ;
545
582
}
546
- Ok ( None ) => {
583
+ Ok ( PullResult :: EndOfGeneration { max_generation } ) => {
584
+ // If there are no more generations to pull, we're done.
585
+ if generation >= max_generation {
586
+ break ;
587
+ }
588
+ insert_handle. end ( ) ?;
547
589
sync_ctx. write_metadata ( ) . await ?;
548
- return Ok ( crate :: database:: Replicated {
549
- frame_no : None ,
550
- frames_synced : 1 ,
551
- } ) ;
552
- }
553
- Err ( err) => {
554
- tracing:: debug!( "pull_one_frame error: {:?}" , err) ;
590
+
591
+ // TODO: Make this crash-proof.
592
+ conn. wal_checkpoint ( true ) ?;
593
+
594
+ sync_ctx. next_generation ( ) ;
555
595
sync_ctx. write_metadata ( ) . await ?;
556
- return Err ( err) ;
596
+
597
+ insert_handle. begin ( ) ?;
598
+ }
599
+ Err ( e) => {
600
+ tracing:: debug!( "pull_one_frame error: {:?}" , e) ;
601
+ err. replace ( e) ;
602
+ break ;
557
603
}
558
604
}
559
605
}
606
+ // This is crash-proof because we:
607
+ //
608
+ // 1. Write WAL frame first
609
+ // 2. Write new max frame to temporary metadata
610
+ // 3. Atomically rename the temporary metadata to the real metadata
611
+ //
612
+ // If we crash before metadata rename completes, the old metadata still
613
+ // points to last successful frame, allowing safe retry from that point.
614
+ // If we happen to have the frame already in the WAL, it's fine to re-pull
615
+ // because append locally is idempotent.
616
+ insert_handle. end ( ) ?;
617
+ sync_ctx. write_metadata ( ) . await ?;
618
+
619
+ if let Some ( err) = err {
620
+ Err ( err)
621
+ } else {
622
+ Ok ( crate :: database:: Replicated {
623
+ frame_no : None ,
624
+ frames_synced : 1 ,
625
+ } )
626
+ }
560
627
}
0 commit comments