@@ -58,6 +58,8 @@ fn is_retryable(err: &io::Error) -> bool {
58
58
cfg ! ( target_os = "macos" ) && err. kind ( ) == io:: ErrorKind :: TimedOut
59
59
}
60
60
61
+ static MAX_IO_ATTEMPTS : u32 = 3 ;
62
+
61
63
fn with_retries < T > ( mut func : impl FnMut ( ) -> io:: Result < T > ) -> io:: Result < T > {
62
64
let mut attempts = 0 ;
63
65
let mut last_error_kind: Option < std:: io:: ErrorKind > = None ;
@@ -79,7 +81,7 @@ fn with_retries<T>(mut func: impl FnMut() -> io::Result<T>) -> io::Result<T> {
79
81
Err ( e) if is_retryable ( & e) => {
80
82
last_error_kind = Some ( e. kind ( ) ) ;
81
83
attempts += 1 ;
82
- if attempts >= 3 {
84
+ if attempts >= MAX_IO_ATTEMPTS {
83
85
return Err ( e) ;
84
86
}
85
87
}
@@ -782,6 +784,7 @@ pub fn relative_path_from_system(path: &Path) -> buck2_error::Result<Cow<'_, Rel
782
784
783
785
#[ cfg( test) ]
784
786
mod tests {
787
+ use std:: collections:: HashMap ;
785
788
use std:: fs;
786
789
use std:: fs:: File ;
787
790
use std:: io;
@@ -804,6 +807,7 @@ mod tests {
804
807
use crate :: fs:: fs_util:: symlink_metadata;
805
808
use crate :: fs:: fs_util:: write;
806
809
use crate :: fs:: fs_util:: IoError ;
810
+ use crate :: fs:: fs_util:: MAX_IO_ATTEMPTS ;
807
811
use crate :: fs:: paths:: abs_norm_path:: AbsNormPath ;
808
812
use crate :: fs:: paths:: abs_path:: AbsPath ;
809
813
use crate :: fs:: paths:: forward_rel_path:: ForwardRelativePath ;
@@ -1337,41 +1341,69 @@ mod tests {
1337
1341
Ok ( ( ) )
1338
1342
}
1339
1343
1340
- #[ test]
1341
- fn test_retry_io ( ) -> buck2_error:: Result < ( ) > {
1342
- let retries = 3 ;
1343
- let tempdir = tempfile:: tempdir ( ) ?;
1344
- let path = tempdir. path ( ) . join ( "test" ) ;
1345
- std:: fs:: write ( & path, "test" ) ?;
1346
- let mut attempts = 0 ;
1344
+ static TEST_FILE_CONTENT : & str = "test" ;
1347
1345
1346
+ fn check_io_with_retry (
1347
+ path : & Path ,
1348
+ error_kind : std:: io:: ErrorKind ,
1349
+ expected_attempts : u32 ,
1350
+ should_succeed : bool ,
1351
+ ) {
1352
+ let mut attempts: u32 = 0 ;
1348
1353
let mut open_fn = |p : & Path | -> io:: Result < File > {
1349
1354
attempts += 1 ;
1350
- if attempts >= retries {
1355
+ if attempts >= MAX_IO_ATTEMPTS {
1351
1356
std:: fs:: File :: open ( p)
1352
1357
} else {
1353
- Err ( io:: Error :: new ( io :: ErrorKind :: TimedOut , "timed out" ) )
1358
+ Err ( io:: Error :: new ( error_kind , error_kind . to_string ( ) ) )
1354
1359
}
1355
1360
} ;
1361
+ let io_result = make_error_with_retry ! ( open_fn( path) , format!( "test123" ) ) ;
1356
1362
1357
- let file = make_error_with_retry ! ( open_fn( & path) , format!( "test123" ) ) ;
1358
-
1359
- #[ cfg( target_os = "macos" ) ]
1360
- {
1361
- let mut file = file?;
1362
- assert_eq ! ( attempts, retries) ;
1363
+ if should_succeed {
1364
+ let mut file = io_result. unwrap ( ) ;
1365
+ assert_eq ! ( attempts, expected_attempts) ;
1363
1366
let mut buf = String :: new ( ) ;
1364
- io:: Read :: read_to_string ( & mut file, & mut buf) ?;
1365
- assert_eq ! ( buf, "test" ) ;
1367
+ io:: Read :: read_to_string ( & mut file, & mut buf) . unwrap ( ) ;
1368
+ assert_eq ! ( buf, TEST_FILE_CONTENT ) ;
1369
+ } else {
1370
+ assert_eq ! ( io_result. err( ) . map( |e| e. e. kind( ) ) . unwrap( ) , error_kind) ;
1371
+ assert_eq ! ( attempts, expected_attempts) ;
1366
1372
}
1373
+ }
1367
1374
1368
- #[ cfg( not( target_os = "macos" ) ) ]
1369
- {
1370
- assert_eq ! (
1371
- file. err( ) . map( |e| e. e. kind( ) ) . unwrap( ) ,
1372
- io:: ErrorKind :: TimedOut
1373
- ) ;
1374
- assert_eq ! ( attempts, 1 ) ;
1375
+ fn get_test_path ( name : & str , tempdir : & tempfile:: TempDir ) -> std:: path:: PathBuf {
1376
+ let path = tempdir. path ( ) . join ( name) ;
1377
+ std:: fs:: write ( & path, TEST_FILE_CONTENT ) . unwrap ( ) ;
1378
+ path
1379
+ }
1380
+
1381
+ #[ test]
1382
+ fn test_retry_io ( ) -> buck2_error:: Result < ( ) > {
1383
+ use std:: io:: ErrorKind ;
1384
+
1385
+ let tempdir = tempfile:: tempdir ( ) . unwrap ( ) ;
1386
+ let mut test_cases = HashMap :: new ( ) ;
1387
+ // The behavior of these test cases varies by platform
1388
+ let should_succeed = cfg ! ( target_os = "macos" ) ;
1389
+ let expected_attempts = if should_succeed { MAX_IO_ATTEMPTS } else { 1 } ;
1390
+ test_cases. insert (
1391
+ get_test_path ( "test_timeout" , & tempdir) ,
1392
+ ( ErrorKind :: TimedOut , expected_attempts, should_succeed) ,
1393
+ ) ;
1394
+
1395
+ // These test cases should behave the same on all platforms
1396
+ test_cases. insert (
1397
+ get_test_path ( "test_too_many_args" , & tempdir) ,
1398
+ ( ErrorKind :: ArgumentListTooLong , 1 , false ) ,
1399
+ ) ;
1400
+ test_cases. insert (
1401
+ get_test_path ( "test_permission_denied" , & tempdir) ,
1402
+ ( ErrorKind :: PermissionDenied , 1 , false ) ,
1403
+ ) ;
1404
+
1405
+ for ( test_path, results) in test_cases {
1406
+ check_io_with_retry ( & test_path, results. 0 , results. 1 , results. 2 ) ;
1375
1407
}
1376
1408
1377
1409
Ok ( ( ) )
0 commit comments