@@ -43,6 +43,10 @@ impl ExecResult {
4343 matches ! ( self , ExecResult :: Done )
4444 }
4545
46+ fn is_retry_later ( & self ) -> bool {
47+ matches ! ( self , ExecResult :: RetryLater )
48+ }
49+
4650 fn is_failed ( & self ) -> bool {
4751 matches ! ( self , ExecResult :: Failed ( _) )
4852 }
@@ -208,11 +212,16 @@ impl Runner {
208212 Err ( e) => {
209213 logging:: error!(
210214 e;
211- "Failed to execute procedure {}-{}" ,
215+ "Failed to execute procedure {}-{}, retry: {} " ,
212216 self . procedure. type_name( ) ,
213- self . meta. id
217+ self . meta. id,
218+ e. is_retry_later( ) ,
214219 ) ;
215220
221+ if e. is_retry_later ( ) {
222+ return ExecResult :: RetryLater ;
223+ }
224+
216225 self . meta . set_state ( ProcedureState :: Failed ) ;
217226
218227 // Write rollback key so we can skip this procedure while recovering procedures.
@@ -290,7 +299,7 @@ impl Runner {
290299 self . procedure. type_name( ) ,
291300 self . meta. id,
292301 subprocedure. procedure. type_name( ) ,
293- subprocedure. id
302+ subprocedure. id,
294303 ) ;
295304
296305 self . submit_subprocedure ( subprocedure. id , subprocedure. procedure ) ;
@@ -372,7 +381,7 @@ impl Runner {
372381 logging:: info!(
373382 "Procedure {}-{} done" ,
374383 self . procedure. type_name( ) ,
375- self . meta. id
384+ self . meta. id,
376385 ) ;
377386
378387 // Mark the state of this procedure to done.
@@ -701,6 +710,45 @@ mod tests {
701710 check_files ( & object_store, ctx. procedure_id , & [ "0000000000.rollback" ] ) . await ;
702711 }
703712
713+ #[ tokio:: test]
714+ async fn test_execute_on_retry_later_error ( ) {
715+ let mut times = 0 ;
716+
717+ let exec_fn = move |_| {
718+ times += 1 ;
719+ async move {
720+ if times == 1 {
721+ Err ( Error :: retry_later ( MockError :: new ( StatusCode :: Unexpected ) ) )
722+ } else {
723+ Ok ( Status :: Done )
724+ }
725+ }
726+ . boxed ( )
727+ } ;
728+
729+ let retry_later = ProcedureAdapter {
730+ data : "retry_later" . to_string ( ) ,
731+ lock_key : LockKey :: single ( "catalog.schema.table" ) ,
732+ exec_fn,
733+ } ;
734+
735+ let dir = TempDir :: new ( "retry_later" ) . unwrap ( ) ;
736+ let meta = retry_later. new_meta ( ROOT_ID ) ;
737+ let ctx = context_without_provider ( meta. id ) ;
738+ let object_store = test_util:: new_object_store ( & dir) ;
739+ let procedure_store = ProcedureStore :: from ( object_store. clone ( ) ) ;
740+ let mut runner = new_runner ( meta. clone ( ) , Box :: new ( retry_later) , procedure_store) ;
741+
742+ let res = runner. execute_once ( & ctx) . await ;
743+ assert ! ( res. is_retry_later( ) , "{res:?}" ) ;
744+ assert_eq ! ( ProcedureState :: Running , meta. state( ) ) ;
745+
746+ let res = runner. execute_once ( & ctx) . await ;
747+ assert ! ( res. is_done( ) , "{res:?}" ) ;
748+ assert_eq ! ( ProcedureState :: Done , meta. state( ) ) ;
749+ check_files ( & object_store, ctx. procedure_id , & [ "0000000000.commit" ] ) . await ;
750+ }
751+
704752 #[ tokio:: test]
705753 async fn test_child_error ( ) {
706754 let mut times = 0 ;
@@ -733,7 +781,7 @@ mod tests {
733781 let state = ctx. provider . procedure_state ( child_id) . await . unwrap ( ) ;
734782 if state == Some ( ProcedureState :: Failed ) {
735783 // The parent procedure to abort itself if child procedure is failed.
736- Err ( Error :: external ( PlainError :: new (
784+ Err ( Error :: from_error_ext ( PlainError :: new (
737785 "subprocedure failed" . to_string ( ) ,
738786 StatusCode :: Unexpected ,
739787 ) ) )
0 commit comments