Coverage Report

Created: 2026-01-09 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/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 "ratelimit.h"
27
28
0
#define CURL_US_PER_SEC       1000000
29
0
#define CURL_RLIMIT_MIN_CHUNK (16 * 1024)
30
#define CURL_RLIMIT_MAX_STEPS 2   /* 500ms interval */
31
32
void Curl_rlimit_init(struct Curl_rlimit *r,
33
                      curl_off_t rate_per_s,
34
                      curl_off_t burst_per_s,
35
                      const struct curltime *pts)
36
0
{
37
0
  curl_off_t rate_steps;
38
39
0
  DEBUGASSERT(rate_per_s >= 0);
40
0
  DEBUGASSERT(burst_per_s >= rate_per_s || !burst_per_s);
41
0
  DEBUGASSERT(pts);
42
0
  r->step_us = CURL_US_PER_SEC;
43
0
  r->rate_per_step = rate_per_s;
44
0
  r->burst_per_step = burst_per_s;
45
  /* On rates that are multiples of CURL_RLIMIT_MIN_CHUNK, we reduce
46
   * the interval `step_us` from 1 second to smaller steps with at
47
   * most CURL_RLIMIT_MAX_STEPS.
48
   * Smaller means more CPU, but also more precision. */
49
0
  rate_steps = rate_per_s / CURL_RLIMIT_MIN_CHUNK;
50
0
  rate_steps = CURLMIN(rate_steps, CURL_RLIMIT_MAX_STEPS);
51
0
  if(rate_steps >= 2) {
52
0
    r->step_us /= rate_steps;
53
0
    r->rate_per_step /= rate_steps;
54
0
    r->burst_per_step /= rate_steps;
55
0
  }
56
0
  r->tokens = r->rate_per_step;
57
0
  r->spare_us = 0;
58
0
  r->ts = *pts;
59
0
  r->blocked = FALSE;
60
0
}
61
62
void Curl_rlimit_start(struct Curl_rlimit *r, const struct curltime *pts)
63
0
{
64
0
  r->tokens = r->rate_per_step;
65
0
  r->spare_us = 0;
66
0
  r->ts = *pts;
67
0
}
68
69
bool Curl_rlimit_active(struct Curl_rlimit *r)
70
0
{
71
0
  return (r->rate_per_step > 0) || r->blocked;
72
0
}
73
74
bool Curl_rlimit_is_blocked(struct Curl_rlimit *r)
75
0
{
76
0
  return r->blocked;
77
0
}
78
79
static void ratelimit_update(struct Curl_rlimit *r,
80
                             const struct curltime *pts)
81
0
{
82
0
  timediff_t elapsed_us, elapsed_steps;
83
0
  curl_off_t token_gain;
84
85
0
  DEBUGASSERT(r->rate_per_step);
86
0
  if((r->ts.tv_sec == pts->tv_sec) && (r->ts.tv_usec == pts->tv_usec))
87
0
    return;
88
89
0
  elapsed_us = curlx_ptimediff_us(pts, &r->ts);
90
0
  if(elapsed_us < 0) { /* not going back in time */
91
0
    DEBUGASSERT(0);
92
0
    return;
93
0
  }
94
95
0
  elapsed_us += r->spare_us;
96
0
  if(elapsed_us < r->step_us)
97
0
    return;
98
99
  /* we do the update */
100
0
  r->ts = *pts;
101
0
  elapsed_steps = elapsed_us / r->step_us;
102
0
  r->spare_us = elapsed_us % r->step_us;
103
104
  /* How many tokens did we gain since the last update? */
105
0
  if(r->rate_per_step > (CURL_OFF_T_MAX / elapsed_steps))
106
0
    token_gain = CURL_OFF_T_MAX;
107
0
  else {
108
0
    token_gain = r->rate_per_step * elapsed_steps;
109
0
  }
110
111
  /* Limit the token again by the burst rate per second (if set), so we
112
   * do not suddenly have a huge number of tokens after inactivity. */
113
0
  r->tokens += token_gain;
114
0
  if(r->burst_per_step && (r->tokens > r->burst_per_step)) {
115
0
    r->tokens = r->burst_per_step;
116
0
  }
117
0
}
118
119
curl_off_t Curl_rlimit_avail(struct Curl_rlimit *r,
120
                             const struct curltime *pts)
121
0
{
122
0
  if(r->blocked)
123
0
    return 0;
124
0
  else if(r->rate_per_step) {
125
0
    ratelimit_update(r, pts);
126
0
    return r->tokens;
127
0
  }
128
0
  else
129
0
    return CURL_OFF_T_MAX;
130
0
}
131
132
void Curl_rlimit_drain(struct Curl_rlimit *r,
133
                       size_t tokens,
134
                       const struct curltime *pts)
135
0
{
136
0
  if(r->blocked || !r->rate_per_step)
137
0
    return;
138
139
0
  ratelimit_update(r, pts);
140
0
#if SIZEOF_CURL_OFF_T <= SIZEOF_SIZE_T
141
0
  if(tokens > CURL_OFF_T_MAX) {
142
0
    r->tokens = CURL_OFF_T_MIN;
143
0
    return;
144
0
  }
145
0
  else
146
0
#endif
147
0
  {
148
0
    curl_off_t val = (curl_off_t)tokens;
149
0
    if((CURL_OFF_T_MIN + val) < r->tokens)
150
0
      r->tokens -= val;
151
0
    else
152
0
      r->tokens = CURL_OFF_T_MIN;
153
0
  }
154
0
}
155
156
timediff_t Curl_rlimit_wait_ms(struct Curl_rlimit *r,
157
                               const struct curltime *pts)
158
0
{
159
0
  timediff_t wait_us, elapsed_us;
160
161
0
  if(r->blocked || !r->rate_per_step)
162
0
    return 0;
163
0
  ratelimit_update(r, pts);
164
0
  if(r->tokens > 0)
165
0
    return 0;
166
167
  /* How much time will it take tokens to become positive again?
168
   * Deduct `spare_us` and check against already elapsed time */
169
0
  wait_us = (1 + (-r->tokens / r->rate_per_step)) * r->step_us;
170
0
  wait_us -= r->spare_us;
171
172
0
  elapsed_us = curlx_ptimediff_us(pts, &r->ts);
173
0
  if(elapsed_us >= wait_us)
174
0
    return 0;
175
0
  wait_us -= elapsed_us;
176
0
  return (wait_us + 999) / 1000; /* in milliseconds */
177
0
}
178
179
void Curl_rlimit_block(struct Curl_rlimit *r,
180
                       bool activate,
181
                       const struct curltime *pts)
182
0
{
183
0
  if(!activate == !r->blocked)
184
0
    return;
185
186
0
  r->ts = *pts;
187
0
  r->blocked = activate;
188
0
  if(!r->blocked) {
189
    /* Start rate limiting fresh. The amount of time this was blocked
190
     * does not generate extra tokens. */
191
0
    Curl_rlimit_start(r, pts);
192
0
  }
193
0
  else {
194
0
    r->tokens = 0;
195
0
  }
196
0
}