@@ -7,8 +7,9 @@ use crate::ops;
7
7
use crate :: sources:: registry:: download;
8
8
use crate :: sources:: registry:: MaybeLock ;
9
9
use crate :: sources:: registry:: { LoadResponse , RegistryConfig , RegistryData } ;
10
- use crate :: util:: errors:: CargoResult ;
11
- use crate :: util:: { Config , Filesystem , IntoUrl , Progress , ProgressStyle } ;
10
+ use crate :: util:: errors:: { CargoResult , HttpNotSuccessful } ;
11
+ use crate :: util:: network:: Retry ;
12
+ use crate :: util:: { internal, Config , Filesystem , IntoUrl , Progress , ProgressStyle } ;
12
13
use anyhow:: Context ;
13
14
use cargo_util:: paths;
14
15
use curl:: easy:: { HttpVersion , List } ;
@@ -83,15 +84,12 @@ pub struct Downloads<'cfg> {
83
84
/// When a download is started, it is added to this map. The key is a
84
85
/// "token" (see `Download::token`). It is removed once the download is
85
86
/// finished.
86
- pending : HashMap < usize , ( Download , EasyHandle ) > ,
87
- /// Set of paths currently being downloaded, mapped to their tokens .
87
+ pending : HashMap < usize , ( Download < ' cfg > , EasyHandle ) > ,
88
+ /// Set of paths currently being downloaded.
88
89
/// This should stay in sync with `pending`.
89
- pending_ids : HashMap < PathBuf , usize > ,
90
- /// The final result of each download. A pair `(token, result)`. This is a
91
- /// temporary holding area, needed because curl can report multiple
92
- /// downloads at once, but the main loop (`wait`) is written to only
93
- /// handle one at a time.
94
- results : HashMap < PathBuf , Result < CompletedDownload , curl:: Error > > ,
90
+ pending_ids : HashSet < PathBuf > ,
91
+ /// The final result of each download.
92
+ results : HashMap < PathBuf , CargoResult < CompletedDownload > > ,
95
93
/// The next ID to use for creating a token (see `Download::token`).
96
94
next : usize ,
97
95
/// Progress bar.
@@ -100,7 +98,7 @@ pub struct Downloads<'cfg> {
100
98
downloads_finished : usize ,
101
99
}
102
100
103
- struct Download {
101
+ struct Download < ' cfg > {
104
102
/// The token for this download, used as the key of the `Downloads::pending` map
105
103
/// and stored in `EasyHandle` as well.
106
104
token : usize ,
@@ -117,6 +115,9 @@ struct Download {
117
115
/// Statistics updated from the progress callback in libcurl.
118
116
total : Cell < u64 > ,
119
117
current : Cell < u64 > ,
118
+
119
+ /// Logic used to track retrying this download if it's a spurious failure.
120
+ retry : Retry < ' cfg > ,
120
121
}
121
122
122
123
struct CompletedDownload {
@@ -155,7 +156,7 @@ impl<'cfg> HttpRegistry<'cfg> {
155
156
downloads : Downloads {
156
157
next : 0 ,
157
158
pending : HashMap :: new ( ) ,
158
- pending_ids : HashMap :: new ( ) ,
159
+ pending_ids : HashSet :: new ( ) ,
159
160
results : HashMap :: new ( ) ,
160
161
progress : RefCell :: new ( Some ( Progress :: with_style (
161
162
"Fetch" ,
@@ -217,37 +218,60 @@ impl<'cfg> HttpRegistry<'cfg> {
217
218
) ;
218
219
219
220
// Collect the results from the Multi handle.
220
- let pending = & mut self . downloads . pending ;
221
- self . multi . messages ( |msg| {
222
- let token = msg. token ( ) . expect ( "failed to read token" ) ;
223
- let ( _, handle) = & pending[ & token] ;
224
- let result = match msg. result_for ( handle) {
225
- Some ( result) => result,
226
- None => return , // transfer is not yet complete.
227
- } ;
228
-
229
- let ( download, mut handle) = pending. remove ( & token) . unwrap ( ) ;
230
- self . downloads . pending_ids . remove ( & download. path ) . unwrap ( ) ;
231
-
232
- let result = match result {
233
- Ok ( ( ) ) => {
234
- self . downloads . downloads_finished += 1 ;
235
- match handle. response_code ( ) {
236
- Ok ( code) => Ok ( CompletedDownload {
237
- response_code : code,
238
- data : download. data . take ( ) ,
239
- index_version : download
240
- . index_version
241
- . take ( )
242
- . unwrap_or_else ( || UNKNOWN . to_string ( ) ) ,
243
- } ) ,
244
- Err ( e) => Err ( e) ,
221
+ let results = {
222
+ let mut results = Vec :: new ( ) ;
223
+ let pending = & mut self . downloads . pending ;
224
+ self . multi . messages ( |msg| {
225
+ let token = msg. token ( ) . expect ( "failed to read token" ) ;
226
+ let ( _, handle) = & pending[ & token] ;
227
+ if let Some ( result) = msg. result_for ( handle) {
228
+ results. push ( ( token, result) ) ;
229
+ } ;
230
+ } ) ;
231
+ results
232
+ } ;
233
+ for ( token, result) in results {
234
+ let ( mut download, handle) = self . downloads . pending . remove ( & token) . unwrap ( ) ;
235
+ let mut handle = self . multi . remove ( handle) ?;
236
+ let data = download. data . take ( ) ;
237
+ let url = self . full_url ( & download. path ) ;
238
+ let result = match download. retry . r#try ( || {
239
+ result. with_context ( || format ! ( "failed to download from `{}`" , url) ) ?;
240
+ let code = handle. response_code ( ) ?;
241
+ // Keep this list of expected status codes in sync with the codes handled in `load`
242
+ if !matches ! ( code, 200 | 304 | 401 | 404 | 451 ) {
243
+ let url = handle. effective_url ( ) ?. unwrap_or ( & url) ;
244
+ return Err ( HttpNotSuccessful {
245
+ code,
246
+ url : url. to_owned ( ) ,
247
+ body : data,
245
248
}
249
+ . into ( ) ) ;
250
+ }
251
+ Ok ( data)
252
+ } ) {
253
+ Ok ( Some ( data) ) => Ok ( CompletedDownload {
254
+ response_code : handle. response_code ( ) ?,
255
+ data,
256
+ index_version : download
257
+ . index_version
258
+ . take ( )
259
+ . unwrap_or_else ( || UNKNOWN . to_string ( ) ) ,
260
+ } ) ,
261
+ Ok ( None ) => {
262
+ // retry the operation
263
+ let handle = self . multi . add ( handle) ?;
264
+ self . downloads . pending . insert ( token, ( download, handle) ) ;
265
+ continue ;
246
266
}
247
267
Err ( e) => Err ( e) ,
248
268
} ;
269
+
270
+ assert ! ( self . downloads. pending_ids. remove( & download. path) ) ;
249
271
self . downloads . results . insert ( download. path , result) ;
250
- } ) ;
272
+ self . downloads . downloads_finished += 1 ;
273
+ }
274
+
251
275
self . downloads . tick ( ) ?;
252
276
253
277
Ok ( ( ) )
@@ -339,6 +363,8 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
339
363
debug ! ( "downloaded the index file `{}` twice" , path. display( ) )
340
364
}
341
365
366
+ // The status handled here need to be kept in sync with the codes handled
367
+ // in `handle_completed_downloads`
342
368
match result. response_code {
343
369
200 => { }
344
370
304 => {
@@ -355,13 +381,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
355
381
return Poll :: Ready ( Ok ( LoadResponse :: NotFound ) ) ;
356
382
}
357
383
code => {
358
- return Err ( anyhow:: anyhow!(
359
- "server returned unexpected HTTP status code {} for {}\n body: {}" ,
360
- code,
361
- self . full_url( path) ,
362
- str :: from_utf8( & result. data) . unwrap_or( "<invalid utf8>" ) ,
363
- ) )
364
- . into ( ) ;
384
+ return Err ( internal ( format ! ( "unexpected HTTP status code {code}" ) ) ) . into ( ) ;
365
385
}
366
386
}
367
387
@@ -371,13 +391,6 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
371
391
} ) ) ;
372
392
}
373
393
374
- if self . config . offline ( ) {
375
- return Poll :: Ready ( Err ( anyhow:: anyhow!(
376
- "can't download index file from '{}': you are in offline mode (--offline)" ,
377
- self . url
378
- ) ) ) ;
379
- }
380
-
381
394
// Looks like we're going to have to do a network request.
382
395
self . start_fetch ( ) ?;
383
396
@@ -433,9 +446,8 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
433
446
let token = self . downloads . next ;
434
447
self . downloads . next += 1 ;
435
448
debug ! ( "downloading {} as {}" , path. display( ) , token) ;
436
- assert_eq ! (
437
- self . downloads. pending_ids. insert( path. to_path_buf( ) , token) ,
438
- None ,
449
+ assert ! (
450
+ self . downloads. pending_ids. insert( path. to_path_buf( ) ) ,
439
451
"path queued for download more than once"
440
452
) ;
441
453
@@ -496,6 +508,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
496
508
index_version : RefCell :: new ( None ) ,
497
509
total : Cell :: new ( 0 ) ,
498
510
current : Cell :: new ( 0 ) ,
511
+ retry : Retry :: new ( self . config ) ?,
499
512
} ;
500
513
501
514
// Finally add the request we've lined up to the pool of requests that cURL manages.
@@ -613,7 +626,7 @@ impl<'cfg> RegistryData for HttpRegistry<'cfg> {
613
626
let timeout = self
614
627
. multi
615
628
. get_timeout ( ) ?
616
- . unwrap_or_else ( || Duration :: new ( 5 , 0 ) ) ;
629
+ . unwrap_or_else ( || Duration :: new ( 1 , 0 ) ) ;
617
630
self . multi
618
631
. wait ( & mut [ ] , timeout)
619
632
. with_context ( || "failed to wait on curl `Multi`" ) ?;
0 commit comments