@@ -4,10 +4,10 @@ use std::pin::Pin;
44use std:: sync:: atomic:: { AtomicBool , AtomicU32 , Ordering } ;
55use std:: sync:: Arc ;
66use std:: task:: { Context , Poll } ;
7+ use std:: time:: Duration ;
78use tempfile:: tempdir;
89use tokio:: io:: { duplex, AsyncRead , AsyncWrite , DuplexStream } ;
910use tower:: Service ;
10- use std:: time:: Duration ;
1111
1212#[ tokio:: test]
1313async fn test_sync_context_push_frame ( ) {
@@ -30,10 +30,10 @@ async fn test_sync_context_push_frame() {
3030 // Push a frame and verify the response
3131 let durable_frame = sync_ctx. push_one_frame ( frame, 1 , 0 ) . await . unwrap ( ) ;
3232 sync_ctx. write_metadata ( ) . await . unwrap ( ) ;
33- assert_eq ! ( durable_frame, 1 ) ; // First frame should return max_frame_no = 1
33+ assert_eq ! ( durable_frame, 0 ) ; // First frame should return max_frame_no = 0
3434
3535 // Verify internal state was updated
36- assert_eq ! ( sync_ctx. durable_frame_num( ) , 1 ) ;
36+ assert_eq ! ( sync_ctx. durable_frame_num( ) , 0 ) ;
3737 assert_eq ! ( sync_ctx. generation( ) , 1 ) ;
3838 assert_eq ! ( server. frame_count( ) , 1 ) ;
3939}
@@ -58,7 +58,7 @@ async fn test_sync_context_with_auth() {
5858
5959 let durable_frame = sync_ctx. push_one_frame ( frame, 1 , 0 ) . await . unwrap ( ) ;
6060 sync_ctx. write_metadata ( ) . await . unwrap ( ) ;
61- assert_eq ! ( durable_frame, 1 ) ;
61+ assert_eq ! ( durable_frame, 0 ) ;
6262 assert_eq ! ( server. frame_count( ) , 1 ) ;
6363}
6464
@@ -84,8 +84,8 @@ async fn test_sync_context_multiple_frames() {
8484 let frame = Bytes :: from ( format ! ( "frame data {}" , i) ) ;
8585 let durable_frame = sync_ctx. push_one_frame ( frame, 1 , i) . await . unwrap ( ) ;
8686 sync_ctx. write_metadata ( ) . await . unwrap ( ) ;
87- assert_eq ! ( durable_frame, i + 1 ) ;
88- assert_eq ! ( sync_ctx. durable_frame_num( ) , i + 1 ) ;
87+ assert_eq ! ( durable_frame, i) ;
88+ assert_eq ! ( sync_ctx. durable_frame_num( ) , i) ;
8989 assert_eq ! ( server. frame_count( ) , i + 1 ) ;
9090 }
9191}
@@ -110,7 +110,7 @@ async fn test_sync_context_corrupted_metadata() {
110110 let frame = Bytes :: from ( "test frame data" ) ;
111111 let durable_frame = sync_ctx. push_one_frame ( frame, 1 , 0 ) . await . unwrap ( ) ;
112112 sync_ctx. write_metadata ( ) . await . unwrap ( ) ;
113- assert_eq ! ( durable_frame, 1 ) ;
113+ assert_eq ! ( durable_frame, 0 ) ;
114114 assert_eq ! ( server. frame_count( ) , 1 ) ;
115115
116116 // Update metadata path to use -info instead of .meta
@@ -132,11 +132,69 @@ async fn test_sync_context_corrupted_metadata() {
132132 assert_eq ! ( sync_ctx. generation( ) , 1 ) ;
133133}
134134
135+ #[ tokio:: test]
136+ async fn test_sync_restarts_with_lower_max_frame_no ( ) {
137+ let _ = tracing_subscriber:: fmt:: try_init ( ) ;
138+
139+ let server = MockServer :: start ( ) ;
140+ let temp_dir = tempdir ( ) . unwrap ( ) ;
141+ let db_path = temp_dir. path ( ) . join ( "test.db" ) ;
142+
143+ // Create initial sync context and push a frame
144+ let sync_ctx = SyncContext :: new (
145+ server. connector ( ) ,
146+ db_path. to_str ( ) . unwrap ( ) . to_string ( ) ,
147+ server. url ( ) ,
148+ None ,
149+ )
150+ . await
151+ . unwrap ( ) ;
152+
153+ let mut sync_ctx = sync_ctx;
154+ let frame = Bytes :: from ( "test frame data" ) ;
155+ let durable_frame = sync_ctx. push_one_frame ( frame. clone ( ) , 1 , 0 ) . await . unwrap ( ) ;
156+ sync_ctx. write_metadata ( ) . await . unwrap ( ) ;
157+ assert_eq ! ( durable_frame, 0 ) ;
158+ assert_eq ! ( server. frame_count( ) , 1 ) ;
159+
160+ // Bump the durable frame num so that the next time we call the
161+ // server we think we are further ahead than the database we are talking to is.
162+ sync_ctx. durable_frame_num += 3 ;
163+ sync_ctx. write_metadata ( ) . await . unwrap ( ) ;
164+
165+ // Create new sync context with corrupted metadata
166+ let mut sync_ctx = SyncContext :: new (
167+ server. connector ( ) ,
168+ db_path. to_str ( ) . unwrap ( ) . to_string ( ) ,
169+ server. url ( ) ,
170+ None ,
171+ )
172+ . await
173+ . unwrap ( ) ;
174+
175+ // Verify that the context was set to new fake values.
176+ assert_eq ! ( sync_ctx. durable_frame_num( ) , 3 ) ;
177+ assert_eq ! ( sync_ctx. generation( ) , 1 ) ;
178+
179+ let frame_no = sync_ctx. durable_frame_num ( ) + 1 ;
180+ // This push should fail because we are ahead of the server and thus should get an invalid
181+ // frame no error.
182+ sync_ctx
183+ . push_one_frame ( frame. clone ( ) , 1 , frame_no)
184+ . await
185+ . unwrap_err ( ) ;
186+
187+ let frame_no = sync_ctx. durable_frame_num ( ) + 1 ;
188+ // This then should work because when the last one failed it updated our state of the server
189+ // durable_frame_num and we should then start writing from there.
190+ sync_ctx. push_one_frame ( frame, 1 , frame_no) . await . unwrap ( ) ;
191+ }
192+
135193#[ tokio:: test]
136194async fn test_sync_context_retry_on_error ( ) {
137195 // Pause time to control it manually
138196 tokio:: time:: pause ( ) ;
139-
197+
140198 let server = MockServer :: start ( ) ;
141199 let temp_dir = tempdir ( ) . unwrap ( ) ;
142200 let db_path = temp_dir. path ( ) . join ( "test.db" ) ;
@@ -172,7 +230,7 @@ async fn test_sync_context_retry_on_error() {
172230 // Next attempt should succeed
173231 let durable_frame = sync_ctx. push_one_frame ( frame, 1 , 0 ) . await . unwrap ( ) ;
174232 sync_ctx. write_metadata ( ) . await . unwrap ( ) ;
175- assert_eq ! ( durable_frame, 1 ) ;
233+ assert_eq ! ( durable_frame, 0 ) ;
176234 assert_eq ! ( server. frame_count( ) , 1 ) ;
177235}
178236
@@ -316,8 +374,9 @@ impl MockServer {
316374 let current_count = frame_count. fetch_add ( 1 , Ordering :: SeqCst ) ;
317375
318376 if req. uri ( ) . path ( ) . contains ( "/sync/" ) {
377+ // Return the max_frame_no that has been accepted
319378 let response = serde_json:: json!( {
320- "max_frame_no" : current_count + 1
379+ "max_frame_no" : current_count
321380 } ) ;
322381
323382 Ok :: < _ , hyper:: Error > (
0 commit comments