mirror of
https://github.com/curl/curl.git
synced 2026-04-15 01:05:56 +08:00
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
101 lines
3.9 KiB
Markdown
101 lines
3.9 KiB
Markdown
<!--
|
|
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.
|