@@ -18,62 +18,76 @@ pub fn download(req: &mut dyn RequestExt) -> EndpointResult {
1818 let mut crate_name = req. params ( ) [ "crate_id" ] . clone ( ) ;
1919 let version = req. params ( ) [ "version" ] . as_str ( ) ;
2020
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 ( ) ) ,
5132 }
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+ } ;
6834
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 {
6954 app. instance_metrics
70- . downloads_unconditional_redirects_total
55+ . downloads_non_canonical_crate_name_total
7156 . inc ( ) ;
72- log_metadata = Some ( ( "unconditional_redirect " , "true " ) ) ;
57+ log_metadata = Some ( ( "bot " , "dl " ) ) ;
7358 }
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" ) ) ;
7585 }
7686
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+
7791 let redirect_url = req
7892 . app ( )
7993 . config
0 commit comments