This is caused by accounting in pv
, which effectively means its rate-limiting is read-limited rather than write-limited. Looking at the source code shows that rate-limiting is driven by a “target”, which is the amount remaining to send. If rate-limiting is on, once per rate limit evaluation cycle, the target is increased by however much we’re supposed to send according to the rate limit; the target is then decreased by however much is actually written. This means that if you set the rate limit to a value larger than the actual write capacity, the target will keep going up; reducing the rate limit won’t then have any effect until pv
has caught up with its target (including what it’s allowed to write according to the new rate limit).
To see this in action, start a basic pv
:
pv /dev/zero /dev/null
Then control that:
pv -R 32605 -L 1M; sleep 10; pv -R 32605 -L 1G; sleep 1; pv -R 32605 -L 1M
You’ll see the impact of the target calculations by varying the duration of the second sleep...
Because of the write limitation, this only causes an issue when you set the rate limit to a value greater than the write capacity.
In a little more detail, here’s how the accounting works with a flow initially limited to 1M, then to 1G for 5s, then back to 1M, on a connection capable of transmitting 400M:
Time Rate Target Sent Remaining
1 1M 1M 1M 0
2 1G 1G 400M 600M
3 1G 1.6G 400M 1.2G
4 1G 2.2G 400M 1.8G
5 1G 2.8G 400M 2.4G
6 1G 3.4G 400M 3G
7 1M 3001M 400M 2601M
8 1M 2602M 400M 2202M
9 1M 2203M 400M 1803M
10 1M 1804M 400M 1404M
11 1M 1405M 400M 1005M
12 1M 1006M 400M 606M
13 1M 607M 400M 207M
14 1M 208M 208M 0
15 1M 1M 1M 0
It takes 7s for the rate limit to be applied again. The longer the time spent with a high rate limit, the longer it takes for the reduced rate limit to be enforced...
The fix for this is quite straightforward, if you can recompile pv
: in loop.c
, change line 154 to target =
(from target +=
), resulting in
|| (cur_time.tv_sec == next_ratecheck.tv_sec
&& cur_time.tv_usec >=
next_ratecheck.tv_usec)) {
target =
((long double) (state->rate_limit)) /
(long double) (1000000 /
RATE_GRANULARITY);
Once that’s done, rate limit reductions are applied immediately (well, within one rate-limit cycle).
pv -L 10M </dev/zero
as a source to thepv
under test can exclude a possible buffering issue