/src/CMake/Utilities/cmcurl/lib/ratelimit.c
Line | Count | Source |
1 | | /*************************************************************************** |
2 | | * _ _ ____ _ |
3 | | * Project ___| | | | _ \| | |
4 | | * / __| | | | |_) | | |
5 | | * | (__| |_| | _ <| |___ |
6 | | * \___|\___/|_| \_\_____| |
7 | | * |
8 | | * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | | * |
10 | | * This software is licensed as described in the file COPYING, which |
11 | | * you should have received as part of this distribution. The terms |
12 | | * are also available at https://curl.se/docs/copyright.html. |
13 | | * |
14 | | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | | * copies of the Software, and permit persons to whom the Software is |
16 | | * furnished to do so, under the terms of the COPYING file. |
17 | | * |
18 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | | * KIND, either express or implied. |
20 | | * |
21 | | * SPDX-License-Identifier: curl |
22 | | * |
23 | | ***************************************************************************/ |
24 | | #include "curl_setup.h" |
25 | | |
26 | | #include "urldata.h" |
27 | | #include "ratelimit.h" |
28 | | |
29 | 0 | #define CURL_US_PER_SEC 1000000 |
30 | 0 | #define CURL_RLIMIT_MIN_RATE (4 * 1024) /* minimum step rate */ |
31 | 0 | #define CURL_RLIMIT_STEP_MIN_MS 2 /* minimum step duration */ |
32 | | |
33 | | static void rlimit_update(struct Curl_rlimit *r, |
34 | | const struct curltime *pts) |
35 | 0 | { |
36 | 0 | timediff_t elapsed_us, elapsed_steps; |
37 | 0 | int64_t token_gain; |
38 | |
|
39 | 0 | DEBUGASSERT(r->rate_per_step); |
40 | 0 | if((r->ts.tv_sec == pts->tv_sec) && (r->ts.tv_usec == pts->tv_usec)) |
41 | 0 | return; |
42 | | |
43 | 0 | elapsed_us = curlx_ptimediff_us(pts, &r->ts); |
44 | 0 | if(elapsed_us < 0) { /* not going back in time */ |
45 | 0 | DEBUGASSERT(0); |
46 | 0 | return; |
47 | 0 | } |
48 | | |
49 | 0 | elapsed_us += r->spare_us; |
50 | 0 | if(elapsed_us < r->step_us) |
51 | 0 | return; |
52 | | |
53 | | /* we do the update */ |
54 | 0 | r->ts = *pts; |
55 | 0 | elapsed_steps = elapsed_us / r->step_us; |
56 | 0 | r->spare_us = elapsed_us % r->step_us; |
57 | | |
58 | | /* How many tokens did we gain since the last update? */ |
59 | 0 | if(r->rate_per_step > (INT64_MAX / elapsed_steps)) |
60 | 0 | token_gain = INT64_MAX; |
61 | 0 | else { |
62 | 0 | token_gain = r->rate_per_step * elapsed_steps; |
63 | 0 | } |
64 | |
|
65 | 0 | if((INT64_MAX - token_gain) > r->tokens) |
66 | 0 | r->tokens += token_gain; |
67 | 0 | else |
68 | 0 | r->tokens = INT64_MAX; |
69 | | |
70 | | /* Limit the token again by the burst rate (if set), so we |
71 | | * do not suddenly have a huge number of tokens after inactivity. */ |
72 | 0 | if(r->burst_per_step && (r->tokens > r->burst_per_step)) { |
73 | 0 | r->tokens = r->burst_per_step; |
74 | 0 | } |
75 | 0 | } |
76 | | |
77 | | static void rlimit_tune_steps(struct Curl_rlimit *r, |
78 | | int64_t tokens_total) |
79 | 0 | { |
80 | 0 | int64_t tokens_last, tokens_main, msteps; |
81 | | |
82 | | /* Tune the ratelimit at the start *if* we know how many tokens |
83 | | * are expected to be consumed in total. |
84 | | * The reason for tuning is that rlimit provides tokens to be consumed |
85 | | * per "step" which starts out to be a second. The tokens may be consumed |
86 | | * in full at the beginning of a step. The remainder of the second will |
87 | | * have no tokens available, effectively blocking the consumption and |
88 | | * so keeping the "step average" in line. |
89 | | * This works will up to the last step. When no more tokens are needed, |
90 | | * no wait will happen and the last step would be too fast. This is |
91 | | * especially noticeable when only a few steps are needed. |
92 | | * |
93 | | * Example: downloading 1.5kb with a ratelimit of 1k could be done in |
94 | | * roughly 1 second (1k in the first second and the 0.5 at the start of |
95 | | * the second one). |
96 | | * |
97 | | * The tuning tries to make the last step small, using only |
98 | | * 1 percent of the total tokens (at least 1). The rest of the tokens |
99 | | * are to be consumed in the steps before by adjusting the duration of |
100 | | * the step and the amount of tokens it provides. */ |
101 | 0 | if(!r->rate_per_step || |
102 | 0 | (tokens_total <= 1) || |
103 | 0 | (tokens_total > (INT64_MAX / 1000))) |
104 | 0 | return; |
105 | | |
106 | | /* Calculate tokens for the last step and the ones before. */ |
107 | 0 | tokens_last = tokens_total / 100; |
108 | 0 | if(!tokens_last) /* less than 100 total, use 1 */ |
109 | 0 | tokens_last = 1; |
110 | 0 | else if(tokens_last > CURL_RLIMIT_MIN_RATE) |
111 | 0 | tokens_last = CURL_RLIMIT_MIN_RATE; |
112 | 0 | DEBUGASSERT(tokens_last); |
113 | 0 | tokens_main = tokens_total - tokens_last; |
114 | 0 | DEBUGASSERT(tokens_main); |
115 | | |
116 | | /* how many milli-steps will it take to consume those, give the |
117 | | * original rate limit per second? */ |
118 | 0 | DEBUGASSERT(r->step_us == CURL_US_PER_SEC); |
119 | |
|
120 | 0 | msteps = (tokens_main * 1000 / r->rate_per_step); |
121 | 0 | if(msteps < CURL_RLIMIT_STEP_MIN_MS) { |
122 | | /* Steps this small will not work. Do not tune. */ |
123 | 0 | return; |
124 | 0 | } |
125 | 0 | else if(msteps < 1000) { |
126 | | /* It needs less than one step to provide the needed tokens. |
127 | | * Make it exactly that long and with exactly those tokens. */ |
128 | 0 | r->step_us = (timediff_t)msteps * 1000; |
129 | 0 | r->rate_per_step = tokens_main; |
130 | 0 | r->tokens = r->rate_per_step; |
131 | 0 | } |
132 | 0 | else { |
133 | | /* More than 1 step. Spread the remainder milli steps and |
134 | | * the tokens they need to provide across all steps. If integer |
135 | | * arithmetic can do it. */ |
136 | 0 | curl_off_t ms_unaccounted = (msteps % 1000); |
137 | 0 | curl_off_t mstep_inc = (ms_unaccounted / (msteps / 1000)); |
138 | 0 | if(mstep_inc) { |
139 | 0 | curl_off_t rate_inc = ((r->rate_per_step * mstep_inc) / 1000); |
140 | 0 | if(rate_inc) { |
141 | 0 | r->step_us = CURL_US_PER_SEC + ((timediff_t)mstep_inc * 1000); |
142 | 0 | r->rate_per_step += rate_inc; |
143 | 0 | r->tokens = r->rate_per_step; |
144 | 0 | } |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | 0 | if(r->burst_per_step) |
149 | 0 | r->burst_per_step = r->rate_per_step; |
150 | 0 | } |
151 | | |
152 | | void Curl_rlimit_init(struct Curl_rlimit *r, |
153 | | int64_t rate_per_sec, |
154 | | int64_t burst_per_sec, |
155 | | const struct curltime *pts) |
156 | 0 | { |
157 | 0 | DEBUGASSERT(rate_per_sec >= 0); |
158 | 0 | DEBUGASSERT(burst_per_sec >= rate_per_sec || !burst_per_sec); |
159 | 0 | DEBUGASSERT(pts); |
160 | 0 | r->rate_per_step = rate_per_sec; |
161 | 0 | r->burst_per_step = burst_per_sec; |
162 | 0 | r->step_us = CURL_US_PER_SEC; |
163 | 0 | r->spare_us = 0; |
164 | 0 | r->tokens = r->rate_per_step; |
165 | 0 | r->ts = *pts; |
166 | 0 | r->blocked = FALSE; |
167 | 0 | } |
168 | | |
169 | | void Curl_rlimit_start(struct Curl_rlimit *r, const struct curltime *pts, |
170 | | int64_t total_tokens) |
171 | 0 | { |
172 | 0 | r->tokens = r->rate_per_step; |
173 | 0 | r->spare_us = 0; |
174 | 0 | r->ts = *pts; |
175 | 0 | rlimit_tune_steps(r, total_tokens); |
176 | 0 | } |
177 | | |
178 | | int64_t Curl_rlimit_per_step(struct Curl_rlimit *r) |
179 | 0 | { |
180 | 0 | return r->rate_per_step; |
181 | 0 | } |
182 | | |
183 | | bool Curl_rlimit_active(struct Curl_rlimit *r) |
184 | 0 | { |
185 | 0 | return (r->rate_per_step > 0) || r->blocked; |
186 | 0 | } |
187 | | |
188 | | bool Curl_rlimit_is_blocked(struct Curl_rlimit *r) |
189 | 0 | { |
190 | 0 | return (bool)r->blocked; |
191 | 0 | } |
192 | | |
193 | | int64_t Curl_rlimit_avail(struct Curl_rlimit *r, |
194 | | const struct curltime *pts) |
195 | 0 | { |
196 | 0 | if(r->blocked) |
197 | 0 | return 0; |
198 | 0 | else if(r->rate_per_step) { |
199 | 0 | rlimit_update(r, pts); |
200 | 0 | return r->tokens; |
201 | 0 | } |
202 | 0 | else |
203 | 0 | return INT64_MAX; |
204 | 0 | } |
205 | | |
206 | | void Curl_rlimit_drain(struct Curl_rlimit *r, |
207 | | size_t tokens, |
208 | | const struct curltime *pts) |
209 | 0 | { |
210 | 0 | if(r->blocked || !r->rate_per_step) |
211 | 0 | return; |
212 | | |
213 | 0 | rlimit_update(r, pts); |
214 | 0 | #if 8 <= SIZEOF_SIZE_T |
215 | 0 | if(tokens > INT64_MAX) { |
216 | 0 | r->tokens = INT64_MAX; |
217 | 0 | } |
218 | 0 | else |
219 | 0 | #endif |
220 | 0 | { |
221 | 0 | int64_t val = (int64_t)tokens; |
222 | 0 | if((INT64_MIN + val) < r->tokens) |
223 | 0 | r->tokens -= val; |
224 | 0 | else |
225 | 0 | r->tokens = INT64_MIN; |
226 | 0 | } |
227 | 0 | } |
228 | | |
229 | | timediff_t Curl_rlimit_wait_ms(struct Curl_rlimit *r, |
230 | | const struct curltime *pts) |
231 | 0 | { |
232 | 0 | timediff_t wait_us, elapsed_us; |
233 | |
|
234 | 0 | if(r->blocked || !r->rate_per_step) |
235 | 0 | return 0; |
236 | 0 | rlimit_update(r, pts); |
237 | 0 | if(r->tokens > 0) |
238 | 0 | return 0; |
239 | | |
240 | | /* How much time will it take tokens to become positive again? |
241 | | * Deduct `spare_us` and check against already elapsed time */ |
242 | 0 | wait_us = r->step_us - r->spare_us; |
243 | 0 | if(r->tokens < 0) { |
244 | 0 | curl_off_t debt_pct = ((-r->tokens) * 100 / r->rate_per_step); |
245 | 0 | if(debt_pct) |
246 | 0 | wait_us += (r->step_us * debt_pct / 100); |
247 | 0 | } |
248 | |
|
249 | 0 | elapsed_us = curlx_ptimediff_us(pts, &r->ts); |
250 | 0 | if(elapsed_us >= wait_us) |
251 | 0 | return 0; |
252 | 0 | wait_us -= elapsed_us; |
253 | 0 | return (wait_us + 999) / 1000; /* in milliseconds */ |
254 | 0 | } |
255 | | |
256 | | timediff_t Curl_rlimit_next_step_ms(struct Curl_rlimit *r, |
257 | | const struct curltime *pts) |
258 | 0 | { |
259 | 0 | if(!r->blocked && r->rate_per_step) { |
260 | 0 | timediff_t elapsed_us, next_us; |
261 | |
|
262 | 0 | elapsed_us = curlx_ptimediff_us(pts, &r->ts) + r->spare_us; |
263 | 0 | if(r->step_us > elapsed_us) { |
264 | 0 | next_us = r->step_us - elapsed_us; |
265 | 0 | return (next_us + 999) / 1000; /* in milliseconds */ |
266 | 0 | } |
267 | 0 | } |
268 | 0 | return 0; |
269 | 0 | } |
270 | | |
271 | | void Curl_rlimit_block(struct Curl_rlimit *r, |
272 | | bool activate, |
273 | | const struct curltime *pts) |
274 | 0 | { |
275 | 0 | if(!activate == !r->blocked) |
276 | 0 | return; |
277 | | |
278 | 0 | r->ts = *pts; |
279 | 0 | r->blocked = activate; |
280 | 0 | if(!r->blocked) { |
281 | | /* Start rate limiting fresh. The amount of time this was blocked |
282 | | * does not generate extra tokens. */ |
283 | 0 | Curl_rlimit_start(r, pts, -1); |
284 | 0 | } |
285 | 0 | else { |
286 | 0 | r->tokens = 0; |
287 | 0 | } |
288 | 0 | } |