mirror of
https://github.com/curl/curl.git
synced 2026-08-25 06:33:32 +03:00
ratelimit: redesign
Description of how this works in `docs/internal/RATELIMITS.ms`. Notable implementation changes: - KEEP_SEND_PAUSE/KEEP_SEND_HOLD and KEEP_RECV_PAUSE/KEEP_RECV_HOLD no longer exist. Pausing is down via blocked the new rlimits. - KEEP_SEND_TIMED no longer exists. Pausing "100-continue" transfers is done in the new `Curl_http_perform_pollset()` method. - HTTP/2 rate limiting implemented via window updates. When transfer initiaiting connection has a ratelimit, adjust the initial window size - HTTP/3 ngtcp2 rate limitin implemnented via ack updates - HTTP/3 quiche does not seem to support this via its API - the default progress-meter has been improved for accuracy in "current speed" results. pytest speed tests have been improved. Closes #19384
This commit is contained in:
parent
bfde781121
commit
24b36fdd15
48 changed files with 1146 additions and 675 deletions
|
|
@ -63,6 +63,7 @@ INTERNALDOCS = \
|
|||
internals/MULTI-EV.md \
|
||||
internals/NEW-PROTOCOL.md \
|
||||
internals/PORTING.md \
|
||||
internals/RATELIMITS.md \
|
||||
internals/README.md \
|
||||
internals/SCORECARD.md \
|
||||
internals/SPLAY.md \
|
||||
|
|
|
|||
100
docs/internals/RATELIMITS.md
Normal file
100
docs/internals/RATELIMITS.md
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<!--
|
||||
Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
|
||||
SPDX-License-Identifier: curl
|
||||
-->
|
||||
|
||||
# Rate Limiting Transfers
|
||||
|
||||
Rate limiting a transfer means that no more than "n bytes per second"
|
||||
shall be sent or received. It can be set individually for both directions
|
||||
via `CURLOPT_MAX_RECV_SPEED_LARGE` and `CURLOPT_MAX_SEND_SPEED_LARGE`. These
|
||||
options may be adjusted for an ongoing transfer.
|
||||
|
||||
### Implementation Base
|
||||
|
||||
`ratelimit.[ch]` implements `struct Curl_rlimit` and functions to manage
|
||||
such limits. It has the following properties:
|
||||
|
||||
* `rate_per_sec`: how many "tokens" can be used per second, 0 for infinite.
|
||||
* `tokens`: the currently available tokens to consume
|
||||
* `burst_per_sec`: an upper limit on tokens available
|
||||
* `ts`: the microsecond timestamp of the last tokens update
|
||||
* `spare_us`: elapsed microseconds that have not counted yet for a token update
|
||||
* `blocked`: if the limit is blocked
|
||||
|
||||
Tokens can be *drained* from an `rlimit`. This reduces `tokens`, even to
|
||||
negative values. To enforce the limits, tokens should not be drained
|
||||
further when they reach 0, but such things may happen.
|
||||
|
||||
An `rlimit`can be asked how long to wait until `tokens` are positive again.
|
||||
This is given in milliseconds. When token are available, this wait
|
||||
time is 0.
|
||||
|
||||
Ideally a user of `rlimit` would consume the available tokens to 0, then
|
||||
get a wait times of 1000ms, after which the set rate of tokens has
|
||||
regenerated. Rinse and repeat.
|
||||
|
||||
Should a user drain twice the amount of the rate, tokens are negative
|
||||
and the wait time is 2 seconds. The `spare_us` account for the
|
||||
time that has passed for the consumption. When a user takes 250ms to
|
||||
consume the rate, the wait time is then 750ms.
|
||||
|
||||
When a user drains nothing for two seconds, the available tokens would
|
||||
grow to twice the rate, unless a burst rate is set.
|
||||
|
||||
Finally, an `rlimit` may be set to `blocked` and later unblocked again.
|
||||
A blocked `rlimit` has no tokens available. This works also when the rate
|
||||
is unlimited (`rate_per_sec` set to 0).
|
||||
|
||||
### Downloads
|
||||
|
||||
`rlimit` is in `data->progress.dl.rlimit`. `setopt.c` initializes it whenever
|
||||
the application sets `CURLOPT_MAX_RECV_SPEED_LARGE`. This may be done
|
||||
in the middle of a transfer.
|
||||
|
||||
`rlimit` tokens are drained in the "protocol" client writer. Checks for
|
||||
capacity depend on the protocol:
|
||||
|
||||
* HTTP and other plain protocols: `transfer.c:sendrecv_dl()` reads only
|
||||
up to capacity.
|
||||
* HTTP/2: capacity is used to adjust a stream's window size. Since all
|
||||
streams start with `64kb`, `rlimit` takes a few seconds to take effect.
|
||||
* HTTP/3: ngtcp2 acknowledges stream data according to capacity. It
|
||||
keeps track of bytes not acknowledged yet. This has the same effect as HTTP/2
|
||||
window sizes.
|
||||
|
||||
(The quiche API does not offer control of `ACK`s and `rlimits` for download
|
||||
do not work in that backend.)
|
||||
|
||||
### Uploads
|
||||
|
||||
`rlimit` is in `data->progress.ul.rlimit`. `setopt.c` initializes it whenever
|
||||
the application sets `CURLOPT_MAX_SEND_SPEED_LARGE`. This may be done
|
||||
in the middle of a transfer.
|
||||
|
||||
The upload capacity is checked in `Curl_client_read()` and readers are
|
||||
only asked to read bytes up to the `rlimit` capacity. This limits upload
|
||||
of data for all protocols in the same way.
|
||||
|
||||
### Pause/Unpause
|
||||
|
||||
Pausing of up-/downloads sets the corresponding `rlimit` to blocked. Unpausing
|
||||
removes that block.
|
||||
|
||||
### Suspending transfers
|
||||
|
||||
While obeying the `rlimit` for up-/download leads to the desired transfer
|
||||
rates, the other issue that needs care is CPU consumption.
|
||||
|
||||
`rlimits` are inspected when computing the "pollset" of a transfer. When
|
||||
a transfer wants to send, but not send tokens are available, the `POLLOUT`
|
||||
is removed from the pollset. Same for receiving.
|
||||
|
||||
For a transfer that is, due to `rlimit`, not able to progress, the pollset
|
||||
is then empty. No socket events are monitored, no CPU activity
|
||||
happens. For paused transfers, this is sufficient.
|
||||
|
||||
Draining `rlimit` happens when a transfer is in `PERFORM` state and
|
||||
exhausted limits cause the timer `TOOFAST` to be set. When the fires,
|
||||
the transfer runs again and `rlimit`s are re-evaluated.
|
||||
Loading…
Add table
Add a link
Reference in a new issue