@@ -18,62 +18,76 @@ pub fn download(req: &mut dyn RequestExt) -> EndpointResult {
18
18
let mut crate_name = req. params ( ) [ "crate_id" ] . clone ( ) ;
19
19
let version = req. params ( ) [ "version" ] . as_str ( ) ;
20
20
21
- let mut log_metadata = None ;
22
- match req. db_conn ( ) {
23
- Ok ( conn) => {
24
- use self :: versions:: dsl:: * ;
25
-
26
- // Returns the crate name as stored in the database, or an error if we could
27
- // not load the version ID from the database.
28
- let ( version_id, canonical_crate_name) = app
29
- . instance_metrics
30
- . downloads_select_query_execution_time
31
- . observe_closure_duration ( || {
32
- versions
33
- . inner_join ( crates:: table)
34
- . select ( ( id, crates:: name) )
35
- . filter ( Crate :: with_name ( & crate_name) )
36
- . filter ( num. eq ( version) )
37
- . first :: < ( i32 , String ) > ( & * conn)
38
- } ) ?;
39
-
40
- if canonical_crate_name != crate_name {
41
- app. instance_metrics
42
- . downloads_non_canonical_crate_name_total
43
- . inc ( ) ;
44
- log_metadata = Some ( ( "bot" , "dl" ) ) ;
45
- }
46
- crate_name = canonical_crate_name;
47
-
48
- // The increment does not happen instantly, but it's deferred to be executed in a batch
49
- // along with other downloads. See crate::downloads_counter for the implementation.
50
- app. downloads_counter . increment ( version_id) ;
21
+ // When no database connection is ready unconditional redirects will be performed. This could
22
+ // happen if the pool is not healthy or if an operator manually configured the application to
23
+ // always perform unconditional redirects (for example as part of the mitigations for an
24
+ // outage). See the comments below for a description of what unconditional redirects do.
25
+ let conn = if app. config . force_unconditional_redirects {
26
+ None
27
+ } else {
28
+ match req. db_conn ( ) {
29
+ Ok ( conn) => Some ( conn) ,
30
+ Err ( PoolError :: UnhealthyPool ) => None ,
31
+ Err ( err) => return Err ( err. into ( ) ) ,
51
32
}
52
- Err ( PoolError :: UnhealthyPool ) => {
53
- // The download endpoint is the most critical route in the whole crates.io application,
54
- // as it's relied upon by users and automations to download crates. Keeping it working
55
- // is the most important thing for us.
56
- //
57
- // The endpoint relies on the database to fetch the canonical crate name (with the
58
- // right capitalization and hyphenation), but that's only needed to serve clients who
59
- // don't call the endpoint with the crate's canonical name.
60
- //
61
- // Thankfully Cargo always uses the right name when calling the endpoint, and we can
62
- // keep it working during a full database outage by unconditionally redirecting without
63
- // checking whether the crate exists or the rigth name is used. Non-Cargo clients might
64
- // get a 404 response instead of a 500, but that's worth it.
65
- //
66
- // Without a working database we also can't count downloads, but that's also less
67
- // critical than keeping Cargo downloads operational.
33
+ } ;
68
34
35
+ let mut log_metadata = None ;
36
+ if let Some ( conn) = & conn {
37
+ use self :: versions:: dsl:: * ;
38
+
39
+ // Returns the crate name as stored in the database, or an error if we could
40
+ // not load the version ID from the database.
41
+ let ( version_id, canonical_crate_name) = app
42
+ . instance_metrics
43
+ . downloads_select_query_execution_time
44
+ . observe_closure_duration ( || {
45
+ versions
46
+ . inner_join ( crates:: table)
47
+ . select ( ( id, crates:: name) )
48
+ . filter ( Crate :: with_name ( & crate_name) )
49
+ . filter ( num. eq ( version) )
50
+ . first :: < ( i32 , String ) > ( & * * conn)
51
+ } ) ?;
52
+
53
+ if canonical_crate_name != crate_name {
69
54
app. instance_metrics
70
- . downloads_unconditional_redirects_total
55
+ . downloads_non_canonical_crate_name_total
71
56
. inc ( ) ;
72
- log_metadata = Some ( ( "unconditional_redirect " , "true " ) ) ;
57
+ log_metadata = Some ( ( "bot " , "dl " ) ) ;
73
58
}
74
- Err ( err) => return Err ( err. into ( ) ) ,
59
+ crate_name = canonical_crate_name;
60
+
61
+ // The increment does not happen instantly, but it's deferred to be executed in a batch
62
+ // along with other downloads. See crate::downloads_counter for the implementation.
63
+ app. downloads_counter . increment ( version_id) ;
64
+ } else {
65
+ // The download endpoint is the most critical route in the whole crates.io application,
66
+ // as it's relied upon by users and automations to download crates. Keeping it working
67
+ // is the most important thing for us.
68
+ //
69
+ // The endpoint relies on the database to fetch the canonical crate name (with the
70
+ // right capitalization and hyphenation), but that's only needed to serve clients who
71
+ // don't call the endpoint with the crate's canonical name.
72
+ //
73
+ // Thankfully Cargo always uses the right name when calling the endpoint, and we can
74
+ // keep it working during a full database outage by unconditionally redirecting without
75
+ // checking whether the crate exists or the rigth name is used. Non-Cargo clients might
76
+ // get a 404 response instead of a 500, but that's worth it.
77
+ //
78
+ // Without a working database we also can't count downloads, but that's also less
79
+ // critical than keeping Cargo downloads operational.
80
+
81
+ app. instance_metrics
82
+ . downloads_unconditional_redirects_total
83
+ . inc ( ) ;
84
+ log_metadata = Some ( ( "unconditional_redirect" , "true" ) ) ;
75
85
}
76
86
87
+ // Ensure the connection is released to the pool as soon as possible, as the download endpoint
88
+ // covers the majority of our traffic and we don't want it to starve other requests.
89
+ drop ( conn) ;
90
+
77
91
let redirect_url = req
78
92
. app ( )
79
93
. config
0 commit comments