@@ -32,6 +32,7 @@ pub mod solvers;
32
32
pub mod types;
33
33
34
34
const DEFAULT_RETRY_INTERVAL : Duration = Duration :: from_secs ( 1 ) ;
35
+ const MAX_RETRY_INTERVAL : Duration = Duration :: from_secs ( 8 ) ;
35
36
static REPLAY_NONCE : http:: HeaderName = http:: HeaderName :: from_static ( "replay-nonce" ) ;
36
37
37
38
pub struct NewCertificateOutput {
55
56
nonce : NoncePool ,
56
57
directory : types:: Directory ,
57
58
solvers : Vec < Box < dyn solvers:: ChallengeSolver + Send + ' a > > ,
59
+ authorization_timeout : Duration ,
60
+ finalize_timeout : Duration ,
61
+ network_error_retries : usize ,
58
62
}
59
63
60
64
#[ derive( Default ) ]
@@ -106,6 +110,9 @@ where
106
110
nonce : Default :: default ( ) ,
107
111
directory : Default :: default ( ) ,
108
112
solvers : Vec :: new ( ) ,
113
+ authorization_timeout : Duration :: from_secs ( 60 ) ,
114
+ finalize_timeout : Duration :: from_secs ( 60 ) ,
115
+ network_error_retries : 3 ,
109
116
} )
110
117
}
111
118
@@ -152,14 +159,14 @@ where
152
159
url : & Uri ,
153
160
payload : P ,
154
161
) -> Result < http:: Response < Bytes > > {
155
- let mut fails = 0 ;
156
-
157
162
let mut nonce = if let Some ( nonce) = self . nonce . get ( ) {
158
163
nonce
159
164
} else {
160
165
self . get_nonce ( ) . await ?
161
166
} ;
162
167
168
+ let mut tries = core:: iter:: repeat ( DEFAULT_RETRY_INTERVAL ) . take ( self . network_error_retries ) ;
169
+
163
170
ngx_log_debug ! ( self . log. as_ptr( ) , "sending request to {url:?}" ) ;
164
171
let res = loop {
165
172
let body = crate :: jws:: sign_jws (
@@ -183,13 +190,15 @@ where
183
190
184
191
let res = match self . http . request ( req) . await {
185
192
Ok ( res) => res,
186
- Err ( e) if fails >= 3 => return Err ( e. into ( ) ) ,
187
- // TODO: limit retries to connection errors
188
- Err ( _) => {
189
- fails += 1 ;
190
- sleep ( DEFAULT_RETRY_INTERVAL ) . await ;
191
- ngx_log_debug ! ( self . log. as_ptr( ) , "retrying: {} of 3" , fails + 1 ) ;
192
- continue ;
193
+ Err ( err) => {
194
+ // TODO: limit retries to connection errors
195
+ if let Some ( tm) = tries. next ( ) {
196
+ sleep ( tm) . await ;
197
+ ngx_log_debug ! ( self . log. as_ptr( ) , "retrying failed request ({err})" ) ;
198
+ continue ;
199
+ } else {
200
+ return Err ( err. into ( ) ) ;
201
+ }
193
202
}
194
203
} ;
195
204
@@ -210,15 +219,13 @@ where
210
219
types:: ErrorKind :: BadNonce | types:: ErrorKind :: RateLimited
211
220
) ;
212
221
213
- if ! retriable || fails >= 3 {
214
- self . nonce . add ( nonce ) ;
215
- return Err ( err . into ( ) ) ;
222
+ if retriable && wait_for_retry ( & res , & mut tries ) . await {
223
+ ngx_log_debug ! ( self . log . as_ptr ( ) , "retrying failed request ({err})" ) ;
224
+ continue ;
216
225
}
217
226
218
- fails += 1 ;
219
-
220
- wait_for_retry ( & res) . await ;
221
- ngx_log_debug ! ( self . log. as_ptr( ) , "retrying: {} of 3" , fails + 1 ) ;
227
+ self . nonce . add ( nonce) ;
228
+ return Err ( err. into ( ) ) ;
222
229
} ;
223
230
224
231
self . nonce . add_from_response ( & res) ;
@@ -381,12 +388,9 @@ where
381
388
}
382
389
} ;
383
390
384
- let mut tries = 10 ;
385
-
386
- while order. status == OrderStatus :: Processing && tries > 0 {
387
- tries -= 1 ;
388
- wait_for_retry ( & res) . await ;
391
+ let mut tries = backoff ( MAX_RETRY_INTERVAL , self . finalize_timeout ) ;
389
392
393
+ while order. status == OrderStatus :: Processing && wait_for_retry ( & res, & mut tries) . await {
390
394
drop ( order) ;
391
395
res = self . post ( & order_url, b"" ) . await ?;
392
396
order = serde_json:: from_slice ( res. body ( ) ) ?;
@@ -432,20 +436,18 @@ where
432
436
return Err ( anyhow ! ( "unexpected challenge status {:?}" , result. status) ) ;
433
437
}
434
438
435
- wait_for_retry ( & res) . await ;
436
-
437
- let mut tries = 10 ;
439
+ let mut tries = backoff ( MAX_RETRY_INTERVAL , self . authorization_timeout ) ;
440
+ wait_for_retry ( & res, & mut tries) . await ;
438
441
439
442
let result = loop {
440
443
let res = self . post ( & url, b"" ) . await ?;
441
444
let result: types:: Authorization = serde_json:: from_slice ( res. body ( ) ) ?;
442
445
443
- if result. status != AuthorizationStatus :: Pending || tries == 0 {
446
+ if result. status != AuthorizationStatus :: Pending
447
+ || !wait_for_retry ( & res, & mut tries) . await
448
+ {
444
449
break result;
445
450
}
446
-
447
- tries -= 1 ;
448
- wait_for_retry ( & res) . await ;
449
451
} ;
450
452
451
453
ngx_log_debug ! (
@@ -499,13 +501,36 @@ pub fn make_certificate_request(
499
501
}
500
502
501
503
/// Waits until the next retry attempt is allowed.
502
- async fn wait_for_retry < B > ( res : & http:: Response < B > ) {
504
+ async fn wait_for_retry < B > (
505
+ res : & http:: Response < B > ,
506
+ policy : & mut impl Iterator < Item = Duration > ,
507
+ ) -> bool {
508
+ let Some ( interval) = policy. next ( ) else {
509
+ return false ;
510
+ } ;
511
+
503
512
let retry_after = res
504
513
. headers ( )
505
514
. get ( http:: header:: RETRY_AFTER )
506
515
. and_then ( parse_retry_after)
507
- . unwrap_or ( DEFAULT_RETRY_INTERVAL ) ;
508
- sleep ( retry_after) . await
516
+ . unwrap_or ( interval) ;
517
+
518
+ sleep ( retry_after) . await ;
519
+ true
520
+ }
521
+
522
+ /// Generate increasing intervals saturated at `max` until `timeout` has passed.
523
+ fn backoff ( max : Duration , timeout : Duration ) -> impl Iterator < Item = Duration > {
524
+ let first = ( Duration :: ZERO , Duration :: from_secs ( 1 ) ) ;
525
+ let stop = Time :: now ( ) + timeout;
526
+
527
+ core:: iter:: successors ( Some ( first) , move |prev : & ( Duration , Duration ) | {
528
+ if Time :: now ( ) >= stop {
529
+ return None ;
530
+ }
531
+ Some ( ( prev. 1 , prev. 0 . saturating_add ( prev. 1 ) ) )
532
+ } )
533
+ . map ( move |( _, x) | x. min ( max) )
509
534
}
510
535
511
536
fn parse_retry_after ( val : & http:: HeaderValue ) -> Option < Duration > {
0 commit comments