@@ -37,6 +37,32 @@ impl BalanceCapacity {
37
37
dl_only_at_percentage : read_env_percentage ( "WEB_CAPACITY_DL_ONLY_PCT" , 80 ) ,
38
38
}
39
39
}
40
+
41
+ /// Handle a request normally
42
+ fn handle ( & self , request : & mut dyn RequestExt ) -> AfterResult {
43
+ self . handler . as_ref ( ) . unwrap ( ) . call ( request)
44
+ }
45
+
46
+ /// Handle a request when load exceeds a threshold
47
+ ///
48
+ /// In report-only mode, log metadata is added but the request is still served. Otherwise,
49
+ /// the request is rejected with a service unavailable response.
50
+ fn handle_high_load ( & self , request : & mut dyn RequestExt , note : & str ) -> AfterResult {
51
+ if self . report_only {
52
+ // In report-only mode we serve all requests but add log metadata
53
+ super :: log_request:: add_custom_metadata ( request, "would_reject" , note) ;
54
+ self . handle ( request)
55
+ } else {
56
+ // Reject the request
57
+ super :: log_request:: add_custom_metadata ( request, "cause" , note) ;
58
+ let body = "Service temporarily unavailable" ;
59
+ Response :: builder ( )
60
+ . status ( StatusCode :: SERVICE_UNAVAILABLE )
61
+ . header ( header:: CONTENT_LENGTH , body. len ( ) )
62
+ . body ( Body :: from_static ( body. as_bytes ( ) ) )
63
+ . map_err ( box_error)
64
+ }
65
+ }
40
66
}
41
67
42
68
impl AroundMiddleware for BalanceCapacity {
@@ -49,50 +75,33 @@ impl Handler for BalanceCapacity {
49
75
fn call ( & self , request : & mut dyn RequestExt ) -> AfterResult {
50
76
// The _drop_on_exit ensures the counter is decremented for all exit paths (including panics)
51
77
let ( _drop_on_exit, count) = RequestCounter :: add_one ( & self . in_flight_requests ) ;
52
- let handler = self . handler . as_ref ( ) . unwrap ( ) ;
53
78
let load = 100 * count / self . capacity ;
54
79
55
80
// Begin logging request count so early stages of load increase can be located
56
81
if load >= self . log_at_percentage {
57
82
super :: log_request:: add_custom_metadata ( request, "in_flight_requests" , count) ;
58
83
}
59
84
60
- // In report-only mode we serve all requests and only enforce the logging limit above
61
- if self . report_only {
62
- return handler. call ( request) ;
63
- }
64
-
65
85
// Download requests are always accepted
66
86
if request. path ( ) . starts_with ( "/api/v1/crates/" ) && request. path ( ) . ends_with ( "/download" ) {
67
- return handler . call ( request) ;
87
+ return self . handle ( request) ;
68
88
}
69
89
70
90
// Reject read-only requests as load nears capacity. Bots are likely to send only safe
71
91
// requests and this helps prioritize requests that users may be reluctant to retry.
72
92
if load >= self . throttle_at_percentage && request. method ( ) . is_safe ( ) {
73
- return over_capacity_response ( request) ;
93
+ return self . handle_high_load ( request, "over capacity (throttle)" ) ;
74
94
}
75
95
76
96
// As load reaches capacity, all non-download requests are rejected
77
97
if load >= self . dl_only_at_percentage {
78
- return over_capacity_response ( request) ;
98
+ return self . handle_high_load ( request, "over capacity (download only)" ) ;
79
99
}
80
100
81
- handler . call ( request)
101
+ self . handle ( request)
82
102
}
83
103
}
84
104
85
- fn over_capacity_response ( request : & mut dyn RequestExt ) -> AfterResult {
86
- // TODO: Generate an alert so we can investigate
87
- super :: log_request:: add_custom_metadata ( request, "cause" , "over capacity" ) ;
88
- let body = "Service temporarily unavailable" ;
89
- Response :: builder ( )
90
- . status ( StatusCode :: SERVICE_UNAVAILABLE )
91
- . header ( header:: CONTENT_LENGTH , body. len ( ) )
92
- . body ( Body :: from_static ( body. as_bytes ( ) ) )
93
- . map_err ( box_error)
94
- }
95
-
96
105
fn read_env_percentage ( name : & str , default : usize ) -> usize {
97
106
if let Ok ( value) = env:: var ( name) {
98
107
value. parse ( ) . unwrap_or ( default)
0 commit comments