@@ -8,6 +8,8 @@ use open_build_service_api as obs;
88use tokio:: sync:: Mutex ;
99use tracing:: instrument;
1010
11+ const INITIAL_INTERVAL : Duration = Duration :: from_millis ( 300 ) ;
12+
1113fn is_client_error ( err : & ( dyn std:: error:: Error + ' static ) ) -> bool {
1214 err. downcast_ref :: < reqwest:: Error > ( )
1315 . and_then ( |e| e. status ( ) )
@@ -30,7 +32,8 @@ fn is_caused_by_client_error(report: &Report) -> bool {
3032 } )
3133}
3234
33- async fn retry_request_impl < T , E , Fut , Func > ( backoff_limit : Duration , func : Func ) -> Result < T >
35+ #[ instrument( skip( func) ) ]
36+ pub async fn retry_request < T , E , Fut , Func > ( func : Func ) -> Result < T >
3437where
3538 Fut : Future < Output = Result < T , E > > ,
3639 Func : FnMut ( ) -> Fut ,
3942 let func = Arc :: new ( Mutex :: new ( func) ) ;
4043 backoff:: future:: retry (
4144 ExponentialBackoff {
42- max_elapsed_time : Some ( backoff_limit) ,
45+ max_elapsed_time : None ,
46+ initial_interval : INITIAL_INTERVAL ,
4347 ..Default :: default ( )
4448 } ,
4549 move || {
@@ -60,32 +64,11 @@ where
6064 . await
6165}
6266
63- #[ instrument( skip( func) ) ]
64- pub async fn retry_request < T , E , Fut , Func > ( func : Func ) -> Result < T >
65- where
66- Fut : Future < Output = Result < T , E > > ,
67- Func : FnMut ( ) -> Fut ,
68- E : Into < Report > ,
69- {
70- const BACKOFF_LIMIT : Duration = Duration :: from_secs ( 10 * 60 ) ; // 10 minutes
71- retry_request_impl ( BACKOFF_LIMIT , func) . await
72- }
73-
74- #[ instrument( skip( func) ) ]
75- pub async fn retry_large_request < T , E , Fut , Func > ( func : Func ) -> Result < T >
76- where
77- Fut : Future < Output = Result < T , E > > ,
78- Func : FnMut ( ) -> Fut ,
79- E : Into < Report > ,
80- {
81- const BACKOFF_LIMIT : Duration = Duration :: from_secs ( 60 * 60 ) ; // 1 hour
82- retry_request_impl ( BACKOFF_LIMIT , func) . await
83- }
84-
8567#[ cfg( test) ]
8668mod tests {
8769 use claim:: * ;
8870 use open_build_service_api as obs;
71+ use rstest:: * ;
8972 use wiremock:: {
9073 matchers:: { method, path_regex} ,
9174 Mock , MockServer , ResponseTemplate ,
@@ -95,10 +78,12 @@ mod tests {
9578
9679 use super :: * ;
9780
98- const LIMIT : Duration = Duration :: from_secs ( 1 ) ;
81+ fn wrap_in_io_error ( err : obs:: Error ) -> std:: io:: Error {
82+ std:: io:: Error :: new ( std:: io:: ErrorKind :: Other , err)
83+ }
9984
100- #[ tokio :: test ]
101- async fn test_retry_on_non_client_errors ( ) {
85+ #[ fixture ]
86+ async fn server ( ) -> MockServer {
10287 let server = MockServer :: start ( ) . await ;
10388
10489 Mock :: given ( method ( "GET" ) )
@@ -116,6 +101,13 @@ mod tests {
116101 . mount ( & server)
117102 . await ;
118103
104+ server
105+ }
106+
107+ #[ rstest]
108+ #[ tokio:: test]
109+ async fn test_retry_on_non_client_errors ( server : impl Future < Output = MockServer > ) {
110+ let server = server. await ;
119111 let client = obs:: Client :: new (
120112 server. uri ( ) . parse ( ) . unwrap ( ) ,
121113 TEST_USER . to_owned ( ) ,
@@ -124,50 +116,89 @@ mod tests {
124116
125117 let mut attempts = 0 ;
126118 assert_err ! (
127- retry_request_impl( LIMIT , || {
128- attempts += 1 ;
129- async { client. project( "500" . to_owned( ) ) . meta( ) . await }
130- } )
119+ tokio:: time:: timeout(
120+ Duration :: from_millis( 2000 ) ,
121+ retry_request( || {
122+ attempts += 1 ;
123+ async { client. project( "500" . to_owned( ) ) . meta( ) . await }
124+ } )
125+ )
131126 . await
132127 ) ;
133128 assert_gt ! ( attempts, 1 ) ;
129+ }
130+
131+ #[ rstest]
132+ #[ tokio:: test]
133+ async fn test_retry_on_nested_non_client_errors ( server : impl Future < Output = MockServer > ) {
134+ let server = server. await ;
135+ let client = obs:: Client :: new (
136+ server. uri ( ) . parse ( ) . unwrap ( ) ,
137+ TEST_USER . to_owned ( ) ,
138+ TEST_PASS . to_owned ( ) ,
139+ ) ;
134140
135141 let mut attempts = 0 ;
136142 assert_err ! (
137- retry_request_impl( LIMIT , || {
138- attempts += 1 ;
139- async {
140- client
141- . project( "500" . to_owned( ) )
142- . meta( )
143- . await
144- . map_err( |e| std:: io:: Error :: new( std:: io:: ErrorKind :: Other , e) )
145- }
146- } )
143+ tokio:: time:: timeout(
144+ Duration :: from_millis( 2000 ) ,
145+ retry_request( || {
146+ attempts += 1 ;
147+ async {
148+ client
149+ . project( "500" . to_owned( ) )
150+ . meta( )
151+ . await
152+ . map_err( wrap_in_io_error)
153+ }
154+ } )
155+ )
147156 . await
148157 ) ;
149158 assert_gt ! ( attempts, 1 ) ;
159+ }
150160
151- attempts = 0 ;
161+ #[ rstest]
162+ #[ tokio:: test]
163+ async fn test_no_retry_on_client_errors ( server : impl Future < Output = MockServer > ) {
164+ let server = server. await ;
165+ let client = obs:: Client :: new (
166+ server. uri ( ) . parse ( ) . unwrap ( ) ,
167+ TEST_USER . to_owned ( ) ,
168+ TEST_PASS . to_owned ( ) ,
169+ ) ;
170+
171+ let mut attempts = 0 ;
152172 assert_err ! (
153- retry_request_impl ( LIMIT , || {
173+ retry_request ( || {
154174 attempts += 1 ;
155175 async { client. project( "403" . to_owned( ) ) . meta( ) . await }
156176 } )
157177 . await
158178 ) ;
159179 assert_eq ! ( attempts, 1 ) ;
180+ }
160181
161- attempts = 0 ;
182+ #[ rstest]
183+ #[ tokio:: test]
184+ async fn test_no_retry_on_nested_client_errors ( server : impl Future < Output = MockServer > ) {
185+ let server = server. await ;
186+ let client = obs:: Client :: new (
187+ server. uri ( ) . parse ( ) . unwrap ( ) ,
188+ TEST_USER . to_owned ( ) ,
189+ TEST_PASS . to_owned ( ) ,
190+ ) ;
191+
192+ let mut attempts = 0 ;
162193 assert_err ! (
163- retry_request_impl ( LIMIT , || {
194+ retry_request ( || {
164195 attempts += 1 ;
165196 async {
166197 client
167198 . project( "403" . to_owned( ) )
168199 . meta( )
169200 . await
170- . map_err( |e| std :: io :: Error :: new ( std :: io :: ErrorKind :: Other , e ) )
201+ . map_err( wrap_in_io_error )
171202 }
172203 } )
173204 . await
0 commit comments