@@ -5,15 +5,43 @@ use chrono::{DateTime, NaiveDateTime, Utc};
5
5
use futures_util:: {
6
6
future:: TryFutureExt ,
7
7
stream:: { FuturesUnordered , StreamExt } ,
8
+ Future ,
8
9
} ;
9
- use rusoto_core:: { region:: Region , RusotoError } ;
10
+ use rusoto_core:: { region:: Region , RusotoError , RusotoResult } ;
10
11
use rusoto_credential:: DefaultCredentialsProvider ;
11
12
use rusoto_s3:: {
12
13
DeleteObjectsRequest , GetObjectError , GetObjectRequest , HeadObjectError , HeadObjectRequest ,
13
14
ListObjectsV2Request , ObjectIdentifier , PutObjectRequest , S3Client , S3 ,
14
15
} ;
15
- use std:: { convert:: TryInto , io:: Write , sync:: Arc } ;
16
- use tokio:: runtime:: Runtime ;
16
+ use std:: { convert:: TryInto , io:: Write , sync:: Arc , time:: Duration } ;
17
+ use tokio:: { runtime:: Runtime , time:: sleep} ;
18
+
19
+ const MAX_ATTEMPT_COUNT : u64 = 3 ;
20
+
21
+ async fn retry_request < F , Fut , T , E > ( mut f : F ) -> RusotoResult < T , E >
22
+ where
23
+ F : FnMut ( ) -> Fut ,
24
+ Fut : Future < Output = RusotoResult < T , E > > ,
25
+ {
26
+ let mut attempts: u64 = 0 ;
27
+ loop {
28
+ match f ( ) . await {
29
+ Err ( RusotoError :: HttpDispatch ( err) ) => {
30
+ attempts += 1 ;
31
+ if attempts >= MAX_ATTEMPT_COUNT {
32
+ break Err ( RusotoError :: HttpDispatch ( err) ) ;
33
+ }
34
+ log:: info!(
35
+ "got HttpDispatchError trying to call S3 (will retry {} more times): {:?}" ,
36
+ MAX_ATTEMPT_COUNT - attempts,
37
+ err
38
+ ) ;
39
+ sleep ( Duration :: from_secs ( attempts) ) . await ;
40
+ }
41
+ result => break result,
42
+ }
43
+ }
44
+ }
17
45
18
46
pub ( super ) struct S3Backend {
19
47
client : S3Client ,
@@ -69,12 +97,14 @@ impl S3Backend {
69
97
70
98
pub ( super ) fn exists ( & self , path : & str ) -> Result < bool , Error > {
71
99
self . runtime . block_on ( async {
72
- let req = HeadObjectRequest {
73
- bucket : self . bucket . clone ( ) ,
74
- key : path. into ( ) ,
75
- ..Default :: default ( )
76
- } ;
77
- let resp = self . client . head_object ( req) . await ;
100
+ let resp = retry_request ( || {
101
+ self . client . head_object ( HeadObjectRequest {
102
+ bucket : self . bucket . clone ( ) ,
103
+ key : path. into ( ) ,
104
+ ..Default :: default ( )
105
+ } )
106
+ } )
107
+ . await ;
78
108
match resp {
79
109
Ok ( _) => Ok ( true ) ,
80
110
Err ( RusotoError :: Service ( HeadObjectError :: NoSuchKey ( _) ) ) => Ok ( false ) ,
@@ -91,24 +121,24 @@ impl S3Backend {
91
121
range : Option < FileRange > ,
92
122
) -> Result < Blob , Error > {
93
123
self . runtime . block_on ( async {
94
- let res = self
95
- . client
96
- . get_object ( GetObjectRequest {
124
+ let res = retry_request ( || {
125
+ self . client . get_object ( GetObjectRequest {
97
126
bucket : self . bucket . to_string ( ) ,
98
127
key : path. into ( ) ,
99
- range : range. map ( |r| format ! ( "bytes={}-{}" , r. start( ) , r. end( ) ) ) ,
128
+ range : range
129
+ . as_ref ( )
130
+ . map ( |r| format ! ( "bytes={}-{}" , r. start( ) , r. end( ) ) ) ,
100
131
..Default :: default ( )
101
132
} )
102
- . await
103
- . map_err ( |err| match err {
104
- RusotoError :: Service ( GetObjectError :: NoSuchKey ( _) ) => {
105
- super :: PathNotFoundError . into ( )
106
- }
107
- RusotoError :: Unknown ( http) if http. status == 404 => {
108
- super :: PathNotFoundError . into ( )
109
- }
110
- err => Error :: from ( err) ,
111
- } ) ?;
133
+ } )
134
+ . await
135
+ . map_err ( |err| match err {
136
+ RusotoError :: Service ( GetObjectError :: NoSuchKey ( _) ) => {
137
+ super :: PathNotFoundError . into ( )
138
+ }
139
+ RusotoError :: Unknown ( http) if http. status == 404 => super :: PathNotFoundError . into ( ) ,
140
+ err => Error :: from ( err) ,
141
+ } ) ?;
112
142
113
143
let mut content = crate :: utils:: sized_buffer:: SizedBuffer :: new ( max_size) ;
114
144
content. reserve (
0 commit comments