From 0e7a77b42a41e664a0ed2410838e901a180753a7 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Wed, 6 May 2026 23:24:51 +0100 Subject: [PATCH 1/5] Allow the rate limit to be specified as a fractional number (#193). --- src/include/options.h | 3 ++- src/include/pv-internal.h | 3 ++- src/include/pv.h | 6 ++++-- src/main/main.c | 2 +- src/main/options.c | 28 ++++++++++++++++++++++------ src/pv/loop.c | 8 ++++---- src/pv/number.c | 23 +++++++++++++++++++---- src/pv/remote.c | 7 ++++--- src/pv/state.c | 3 ++- src/pv/transfer.c | 22 +++++++++++----------- 10 files changed, 71 insertions(+), 34 deletions(-) diff --git a/src/include/options.h b/src/include/options.h index a8719ce..36d25c8 100644 --- a/src/include/options.h +++ b/src/include/options.h @@ -70,7 +70,7 @@ struct opts_s { /*@keep@*/ /*@null@*/ int *watchfd_fd; /* array of fds to watch in each one (0=all) */ /*@keep@*/ /*@null@*/ argv_string *argv; /* array of non-option arguments */ size_t lastwritten; /* show N bytes last written */ - off_t rate_limit; /* rate limit, in bytes per second */ + long double rate_limit; /* rate limit, in bytes per second */ size_t buffer_size; /* buffer size, in bytes (0=default) */ size_t pipe_buffer_size; /* pipe buffer size, in bytes (0=default) */ off_t size; /* total size of data */ @@ -114,6 +114,7 @@ struct opts_s { bool show_stats; /* set to write statistics at the end */ bool width_set_manually; /* width was set manually, not detected */ bool height_set_manually; /* height was set manually, not detected */ + bool rate_limit_active; /* whether a rate limit was set */ }; /*@-exportlocal@*/ diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index 2aff9cb..268b310 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -202,7 +202,7 @@ struct pvstate_s { /*@null@*/ char *output_name; /* name of the output, for diagnostics */ /*@null@*/ char *default_bar_style; /* which bar style to use by default */ off_t error_skip_block; /* skip block size, 0 for adaptive */ - off_t rate_limit; /* rate limit, in bytes per second */ + long double rate_limit; /* rate limit, in bytes per second */ size_t target_buffer_size; /* transfer buffer size (0=default) */ size_t pipe_buffer_size; /* pipe buffer size (0=default) */ off_t size; /* total size of data */ @@ -237,6 +237,7 @@ struct pvstate_s { bool show_stats; /* show statistics on exit */ bool width_set_manually; /* width was set manually, not detected */ bool height_set_manually; /* height was set manually, not detected */ + bool rate_limit_active; /* whether a rate limit is in effect */ } control; /******************* diff --git a/src/include/pv.h b/src/include/pv.h index c7a21c1..a65d405 100644 --- a/src/include/pv.h +++ b/src/include/pv.h @@ -72,8 +72,10 @@ extern double pv_getnum_interval(const char *); * Return the numeric value of a string representing a size, interpreting * suffixes in decimal units (multiples of 1000) instead of multiples of * 1024 if the second argument is true. + * + * Optionally also places the result in a long double. */ -extern off_t pv_getnum_size(const char *, bool); +extern off_t pv_getnum_size(const char *, bool, /*@null@ */ long double *); /* * Return the numeric value of a string representing a count such as screen @@ -264,7 +266,7 @@ extern void pv_state_stop_at_size_set(pvstate_t, bool); extern void pv_state_sync_after_write_set(pvstate_t, bool); extern void pv_state_direct_io_set(pvstate_t, bool); extern void pv_state_sparse_output_set(pvstate_t, bool); -extern void pv_state_rate_limit_set(pvstate_t, off_t); +extern void pv_state_rate_limit_set(pvstate_t, long double, bool); extern void pv_state_target_buffer_size_set(pvstate_t, size_t); extern void pv_state_no_splice_set(pvstate_t, bool); extern void pv_state_pipe_buffer_size_set(pvstate_t, size_t); diff --git a/src/main/main.c b/src/main/main.c index 4eadfe9..4c95137 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -903,7 +903,7 @@ int main(int argc, char **argv) pv_state_sync_after_write_set(state, opts->sync_after_write); pv_state_direct_io_set(state, opts->direct_io); pv_state_discard_input_set(state, opts->discard_input); - pv_state_rate_limit_set(state, opts->rate_limit); + pv_state_rate_limit_set(state, opts->rate_limit, opts->rate_limit_active); pv_state_target_buffer_size_set(state, opts->buffer_size); pv_state_no_splice_set(state, opts->no_splice); pv_state_pipe_buffer_size_set(state, opts->pipe_buffer_size); diff --git a/src/main/options.c b/src/main/options.c index 3f69e95..89d1abb 100644 --- a/src/main/options.c +++ b/src/main/options.c @@ -35,6 +35,9 @@ #ifdef HAVE_FTW_H #include #endif +#if HAVE_MATH_H +#include +#endif void display_help(void); @@ -1005,7 +1008,7 @@ opts_t opts_parse(unsigned int argc, char **argv) case 's': if ('@' != *optarg) { /* A number was passed, not "@". */ - opts->size = pv_getnum_size(optarg, opts->decimal_units); + opts->size = pv_getnum_size(optarg, opts->decimal_units, NULL); } else { /* Permit "@". */ const char *size_file = 1 + optarg; @@ -1054,10 +1057,23 @@ opts_t opts_parse(unsigned int argc, char **argv) } break; case 'L': - opts->rate_limit = pv_getnum_size(optarg, opts->decimal_units); + (void) pv_getnum_size(optarg, opts->decimal_units, &(opts->rate_limit)); +#if HAVE_MATH_H + /*@-unrecog@ *//* splint doesn't know nextafterl(). */ + if (opts->rate_limit < nextafterl(0, INFINITY)) { + opts->rate_limit_active = false; + } else { + opts->rate_limit_active = true; + } + /*@+unrecog@ */ +#else + opts->rate_limit_active = false; + if (opts->rate_limit > 0.00001) + opts->rate_limit_active = true; +#endif break; case 'B': - opts->buffer_size = (size_t) pv_getnum_size(optarg, opts->decimal_units); + opts->buffer_size = (size_t) pv_getnum_size(optarg, opts->decimal_units, NULL); opts->no_splice = true; break; case 'C': @@ -1067,10 +1083,10 @@ opts_t opts_parse(unsigned int argc, char **argv) opts->skip_errors++; break; case 'Z': - opts->error_skip_block = pv_getnum_size(optarg, opts->decimal_units); + opts->error_skip_block = pv_getnum_size(optarg, opts->decimal_units, NULL); break; case 'J': - opts->pipe_buffer_size = (size_t) pv_getnum_size(optarg, opts->decimal_units); + opts->pipe_buffer_size = (size_t) pv_getnum_size(optarg, opts->decimal_units, NULL); break; case 'S': opts->stop_at_size = true; @@ -1214,7 +1230,7 @@ opts_t opts_parse(unsigned int argc, char **argv) if (PV_ACTION_WATCHFD == opts->action) { if (opts->linemode || opts->null_terminated_lines || opts->stop_at_size || (opts->skip_errors > 0) || (opts->buffer_size > 0) - || (opts->rate_limit > 0)) { + || (opts->rate_limit_active)) { /*@-mustfreefresh@ *//* see above */ pv_error("%s", _("cannot use line mode or transfer modifier options when watching file descriptors")); diff --git a/src/pv/loop.c b/src/pv/loop.c index 672ef4c..029d1f0 100644 --- a/src/pv/loop.c +++ b/src/pv/loop.c @@ -572,7 +572,7 @@ int pv_main_loop(pvstate_t state) if (1 == state->flags.trigger_exit) break; - if (state->control.rate_limit > 0) { + if (state->control.rate_limit_active) { pv_elapsedtime_read(&cur_time); if (pv_elapsedtime_compare(&cur_time, &next_ratecheck) > 0) { target += @@ -595,7 +595,7 @@ int pv_main_loop(pvstate_t state) if ((0 < state->control.size) && (state->control.stop_at_size)) { if ((state->control.size < (state->transfer.total_written + cansend)) || ((0 == cansend) - && (0 == state->control.rate_limit))) { + && (!state->control.rate_limit_active))) { cansend = state->control.size - state->transfer.total_written; if (0 >= cansend) { debug("%s", "write limit reached (size explicitly set) - setting EOF flags"); @@ -622,11 +622,11 @@ int pv_main_loop(pvstate_t state) if (state->control.linemode) { state->transfer.total_written += lineswritten; - if (state->control.rate_limit > 0) + if (state->control.rate_limit_active) target -= lineswritten; } else { state->transfer.total_written += written; - if (state->control.rate_limit > 0) + if (state->control.rate_limit_active) target -= written; } diff --git a/src/pv/number.c b/src/pv/number.c index bb8a67a..087a182 100644 --- a/src/pv/number.c +++ b/src/pv/number.c @@ -29,8 +29,11 @@ bool pv_isdigit(char c) * a fractional part, optionally followed by a units suffix such as "K" for * kibibytes. If "decimal_units" is true, suffixes are interpreted as * multiples of 1000, rather than multiples of 1024. + * + * If resultptr is not NULL, it is populated with the result as a long + * double. */ -off_t pv_getnum_size(const char *str, bool decimal_units) +off_t pv_getnum_size(const char *str, bool decimal_units, /*@null@ */ long double *resultptr) { off_t integral_part = 0; off_t fractional_part = 0; @@ -39,8 +42,11 @@ off_t pv_getnum_size(const char *str, bool decimal_units) off_t decimal_multiplier = 0; size_t readpos = 0; - if (NULL == str) + if (NULL == str) { + if (NULL != resultptr) + *resultptr = 0.0; return (off_t) 0; + } /* Skip any non-numeric leading characters. */ while (str[readpos] != '\0' && (!pv_isdigit(str[readpos]))) @@ -156,10 +162,19 @@ off_t pv_getnum_size(const char *str, bool decimal_units) * Add the fractional part, divided by its divisor, to the integral * part, now that the multiplier for the units has been applied. */ + if (NULL != resultptr) { + *resultptr = ((long double) fractional_part) / (long double) fractional_divisor; + *resultptr += (long double) integral_part; + } fractional_part = fractional_part / fractional_divisor; integral_part += fractional_part; - debug("%s [%s] = %lld", str, decimal_units ? "decimal" : "binary", (long long) integral_part); + if (NULL != resultptr) { + debug("%s [%s] = %lld = %Lf", str, decimal_units ? "decimal" : "binary", (long long) integral_part, + *resultptr); + } else { + debug("%s [%s] = %lld", str, decimal_units ? "decimal" : "binary", (long long) integral_part); + } return integral_part; } @@ -211,7 +226,7 @@ double pv_getnum_interval(const char *str) */ unsigned int pv_getnum_count(const char *str, bool decimal_units) { - return (unsigned int) pv_getnum_size(str, decimal_units); + return (unsigned int) pv_getnum_size(str, decimal_units, NULL); } diff --git a/src/pv/remote.c b/src/pv/remote.c index 2b6e399..ba0df2d 100644 --- a/src/pv/remote.c +++ b/src/pv/remote.c @@ -34,7 +34,7 @@ struct remote_msg { bool bytes; /* bytes transferred flag */ bool bufpercent; /* transfer buffer percentage flag */ size_t lastwritten; /* last-written bytes count */ - off_t rate_limit; /* rate limit, in bytes per second */ + long double rate_limit; /* rate limit, in bytes per second */ size_t buffer_size; /* buffer size, in bytes (0=default) */ off_t size; /* total size of data */ double interval; /* interval between updates */ @@ -42,6 +42,7 @@ struct remote_msg { unsigned int height; /* screen height */ bool width_set_manually; /* width was set manually, not detected */ bool height_set_manually; /* height was set manually, not detected */ + bool rate_limit_active; /* whether rate limiting is in effect */ char name[256]; /* flawfinder: ignore */ char format[256]; /* flawfinder: ignore */ char extra_display[256]; /* flawfinder: ignore */ @@ -106,6 +107,7 @@ int pv_remote_set(pvstate_t state, pid_t remote) msgbuf.bufpercent = state->control.format_option.bufpercent; msgbuf.lastwritten = state->control.format_option.lastwritten; msgbuf.rate_limit = state->control.rate_limit; + msgbuf.rate_limit_active = state->control.rate_limit_active; msgbuf.buffer_size = state->control.target_buffer_size; msgbuf.size = state->control.size; msgbuf.interval = state->control.interval; @@ -322,8 +324,7 @@ static bool pv__rxsignal_usr2(pvstate_t state) pv_state_set_format_options(state, format_options); - if (msgbuf.rate_limit > 0) - pv_state_rate_limit_set(state, msgbuf.rate_limit); + pv_state_rate_limit_set(state, msgbuf.rate_limit, msgbuf.rate_limit_active); if (msgbuf.buffer_size > 0) { pv_state_target_buffer_size_set(state, msgbuf.buffer_size); } diff --git a/src/pv/state.c b/src/pv/state.c index 0a82b26..c325c8f 100644 --- a/src/pv/state.c +++ b/src/pv/state.c @@ -649,9 +649,10 @@ void pv_state_discard_input_set(pvstate_t state, bool val) state->control.discard_input = val; } -void pv_state_rate_limit_set(pvstate_t state, off_t val) +void pv_state_rate_limit_set(pvstate_t state, long double val, bool is_active) { state->control.rate_limit = val; + state->control.rate_limit_active = is_active; } void pv_state_target_buffer_size_set(pvstate_t state, size_t val) diff --git a/src/pv/transfer.c b/src/pv/transfer.c index 797994f..5cd3c9b 100644 --- a/src/pv/transfer.c +++ b/src/pv/transfer.c @@ -300,11 +300,11 @@ static ssize_t pv__transfer__write_repeated(int fd, char *buf, size_t count, boo * * At most, the number of bytes read will be the number of bytes remaining * in the input buffer, capped to the number of bytes left until - * state->control.size is reached if state->control.stop_at_size is true. - * If state->control.rate_limit is >0, and/or "max_to_write" is >0, and - * splice() is used, then the maximum number of bytes read will be further - * capped to the value of "max_to_write", since splice() writes as well as - * reads. + * state->control.size is reached if state->control.stop_at_size is true. + * If state->control.rate_limit_active is true, and/or "max_to_write" is >0, + * and splice() is used, then the maximum number of bytes read will be + * further capped to the value of "max_to_write", since splice() writes as + * well as reads. * * If splice() was successfully used, sets state->transfer.splice_used to * true; if it was unsuccessfully used, then @@ -380,7 +380,7 @@ static bool pv__transfer_read(pvstate_t state, int fd, bool *eof_in, bool *eof_o && (0 == state->transfer.to_write)) { size_t bytes_to_splice; - if (state->control.rate_limit > 0 || max_to_write != 0) { + if (state->control.rate_limit_active || max_to_write != 0) { bytes_to_splice = (size_t) max_to_write; } else { bytes_to_splice = bytes_can_read; @@ -1163,10 +1163,10 @@ static char *pv__allocate_aligned_buffer(int outfd, int infd, size_t target_size /* * Transfer some data from "fd" to standard output, timing out after 9/100 - * of a second. If state->control.rate_limit is >0, and/or "allowed" is >0, - * only up to "allowed" bytes can be written. The variables that "eof_in" - * and "eof_out" point to are used to flag that we've finished reading and - * writing respectively. + * of a second. If state->control.rate_limit_active is true, and/or + * "allowed" is >0, only up to "allowed" bytes can be written. The + * variables that "eof_in" and "eof_out" point to are used to flag that + * we've finished reading and writing respectively. * * Returns the number of bytes written, or negative on error (in which case * state->status.exit_status is updated). In line mode, the number of lines @@ -1293,7 +1293,7 @@ ssize_t pv_transfer(pvstate_t state, int fd, bool *eof_in, bool *eof_out, off_t * limiting is active or if "allowed" is > 0. */ state->transfer.to_write = (ssize_t) (state->transfer.read_position - state->transfer.write_position); - if ((state->control.rate_limit > 0) || (allowed > 0)) { + if ((state->control.rate_limit_active) || (allowed > 0)) { if ((off_t) (state->transfer.to_write) > allowed) { state->transfer.to_write = (ssize_t) allowed; } From eae06cf3c06bcef5701ae78490fd4c8f4c078971 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Thu, 7 May 2026 23:00:33 +0100 Subject: [PATCH 2/5] Correct the remote setting of the rate limit so that -R without -L does not turn off the current rate limiting of the remote process (#193). --- src/pv/remote.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pv/remote.c b/src/pv/remote.c index ba0df2d..6f07d98 100644 --- a/src/pv/remote.c +++ b/src/pv/remote.c @@ -324,7 +324,8 @@ static bool pv__rxsignal_usr2(pvstate_t state) pv_state_set_format_options(state, format_options); - pv_state_rate_limit_set(state, msgbuf.rate_limit, msgbuf.rate_limit_active); + if (msgbuf.rate_limit_active) + pv_state_rate_limit_set(state, msgbuf.rate_limit, msgbuf.rate_limit_active); if (msgbuf.buffer_size > 0) { pv_state_target_buffer_size_set(state, msgbuf.buffer_size); } From e7087b101d5b869350c7fbe0c3921c4c59e4d0fd Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Fri, 8 May 2026 21:07:29 +0100 Subject: [PATCH 3/5] Allow "-L 0" to be propagated with "-R" so that a remote process can have its rate limiting turned off as well as on; and update the manual to note this (#193). --- docs/pv.1 | 3 +++ docs/pv.1.md | 4 +++- src/include/options.h | 3 ++- src/include/pv-internal.h | 3 ++- src/include/pv.h | 2 +- src/main/main.c | 2 +- src/main/options.c | 2 ++ src/pv/remote.c | 9 ++++++--- src/pv/state.c | 11 ++++++++++- 9 files changed, 30 insertions(+), 9 deletions(-) diff --git a/docs/pv.1 b/docs/pv.1 index c9730e8..d42f5d8 100644 --- a/docs/pv.1 +++ b/docs/pv.1 @@ -267,6 +267,9 @@ If the file already exists, it will be truncated. .BI \-L\ RATE \fR,\ \fB\-\-rate-limit\ RATE Limit the transfer to a maximum of \fIRATE\fR bytes per second. The same suffixes as \*(lq\fB\-\-size\fR\*(rq can be used. +Decimal values are allowed. +Specifying 0 will turn off rate limiting, which is useful with +\*(lq\fB\-\-remote\fR\*(rq. .TP .BI \-B\ BYTES \fR,\ \fB\-\-buffer-size\ BYTES Use a transfer buffer size of \fIBYTES\fR bytes. diff --git a/docs/pv.1.md b/docs/pv.1.md index 750e7ad..687cc70 100644 --- a/docs/pv.1.md +++ b/docs/pv.1.md @@ -276,7 +276,9 @@ are explicitly switched on will be shown. **-L RATE, \--rate-limit RATE** : Limit the transfer to a maximum of *RATE* bytes per second. The same - suffixes as "**\--size**" can be used. + suffixes as "**\--size**" can be used. Decimal values are allowed. + Specifying 0 will turn off rate limiting, which is useful with + "**\--remote**". **-B BYTES, \--buffer-size BYTES** diff --git a/src/include/options.h b/src/include/options.h index 36d25c8..5c27f59 100644 --- a/src/include/options.h +++ b/src/include/options.h @@ -114,7 +114,8 @@ struct opts_s { bool show_stats; /* set to write statistics at the end */ bool width_set_manually; /* width was set manually, not detected */ bool height_set_manually; /* height was set manually, not detected */ - bool rate_limit_active; /* whether a rate limit was set */ + bool rate_limit_specified; /* whether a rate limit value was given */ + bool rate_limit_active; /* whether rate limiting is in effect (>0) */ }; /*@-exportlocal@*/ diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index 268b310..730e6c7 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -237,7 +237,8 @@ struct pvstate_s { bool show_stats; /* show statistics on exit */ bool width_set_manually; /* width was set manually, not detected */ bool height_set_manually; /* height was set manually, not detected */ - bool rate_limit_active; /* whether a rate limit is in effect */ + bool rate_limit_specified; /* whether a rate limit value was given */ + bool rate_limit_active; /* whether a rate limit is in effect (>0) */ } control; /******************* diff --git a/src/include/pv.h b/src/include/pv.h index a65d405..c6d8abd 100644 --- a/src/include/pv.h +++ b/src/include/pv.h @@ -266,7 +266,7 @@ extern void pv_state_stop_at_size_set(pvstate_t, bool); extern void pv_state_sync_after_write_set(pvstate_t, bool); extern void pv_state_direct_io_set(pvstate_t, bool); extern void pv_state_sparse_output_set(pvstate_t, bool); -extern void pv_state_rate_limit_set(pvstate_t, long double, bool); +extern void pv_state_rate_limit_set(pvstate_t, long double, bool, bool); extern void pv_state_target_buffer_size_set(pvstate_t, size_t); extern void pv_state_no_splice_set(pvstate_t, bool); extern void pv_state_pipe_buffer_size_set(pvstate_t, size_t); diff --git a/src/main/main.c b/src/main/main.c index 4c95137..31613af 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -903,7 +903,7 @@ int main(int argc, char **argv) pv_state_sync_after_write_set(state, opts->sync_after_write); pv_state_direct_io_set(state, opts->direct_io); pv_state_discard_input_set(state, opts->discard_input); - pv_state_rate_limit_set(state, opts->rate_limit, opts->rate_limit_active); + pv_state_rate_limit_set(state, opts->rate_limit, opts->rate_limit_specified, opts->rate_limit_active); pv_state_target_buffer_size_set(state, opts->buffer_size); pv_state_no_splice_set(state, opts->no_splice); pv_state_pipe_buffer_size_set(state, opts->pipe_buffer_size); diff --git a/src/main/options.c b/src/main/options.c index 89d1abb..67012ae 100644 --- a/src/main/options.c +++ b/src/main/options.c @@ -801,6 +801,7 @@ opts_t opts_parse(unsigned int argc, char **argv) opts->width_set_manually = false; opts->height_set_manually = false; + opts->rate_limit_specified = false; do { #ifdef HAVE_GETOPT_LONG @@ -1058,6 +1059,7 @@ opts_t opts_parse(unsigned int argc, char **argv) break; case 'L': (void) pv_getnum_size(optarg, opts->decimal_units, &(opts->rate_limit)); + opts->rate_limit_specified = true; #if HAVE_MATH_H /*@-unrecog@ *//* splint doesn't know nextafterl(). */ if (opts->rate_limit < nextafterl(0, INFINITY)) { diff --git a/src/pv/remote.c b/src/pv/remote.c index 6f07d98..2733967 100644 --- a/src/pv/remote.c +++ b/src/pv/remote.c @@ -42,7 +42,8 @@ struct remote_msg { unsigned int height; /* screen height */ bool width_set_manually; /* width was set manually, not detected */ bool height_set_manually; /* height was set manually, not detected */ - bool rate_limit_active; /* whether rate limiting is in effect */ + bool rate_limit_specified; /* whether a rate limit value was set */ + bool rate_limit_active; /* whether rate limiting is in effect (>0) */ char name[256]; /* flawfinder: ignore */ char format[256]; /* flawfinder: ignore */ char extra_display[256]; /* flawfinder: ignore */ @@ -107,6 +108,7 @@ int pv_remote_set(pvstate_t state, pid_t remote) msgbuf.bufpercent = state->control.format_option.bufpercent; msgbuf.lastwritten = state->control.format_option.lastwritten; msgbuf.rate_limit = state->control.rate_limit; + msgbuf.rate_limit_specified = state->control.rate_limit_specified; msgbuf.rate_limit_active = state->control.rate_limit_active; msgbuf.buffer_size = state->control.target_buffer_size; msgbuf.size = state->control.size; @@ -324,8 +326,9 @@ static bool pv__rxsignal_usr2(pvstate_t state) pv_state_set_format_options(state, format_options); - if (msgbuf.rate_limit_active) - pv_state_rate_limit_set(state, msgbuf.rate_limit, msgbuf.rate_limit_active); + if (msgbuf.rate_limit_specified) + pv_state_rate_limit_set(state, msgbuf.rate_limit, msgbuf.rate_limit_specified, + msgbuf.rate_limit_active); if (msgbuf.buffer_size > 0) { pv_state_target_buffer_size_set(state, msgbuf.buffer_size); } diff --git a/src/pv/state.c b/src/pv/state.c index c325c8f..6e957b8 100644 --- a/src/pv/state.c +++ b/src/pv/state.c @@ -649,9 +649,18 @@ void pv_state_discard_input_set(pvstate_t state, bool val) state->control.discard_input = val; } -void pv_state_rate_limit_set(pvstate_t state, long double val, bool is_active) +/* + * If "was_specified" is true, the limit was specified on the command line. + * This is used with --remote to determine whether to affect the remote + * process's rate limit settings. + * + * if "is_active" is true, the limit ("val") is not zero so rate limiting is + * active. + */ +void pv_state_rate_limit_set(pvstate_t state, long double val, bool was_specified, bool is_active) { state->control.rate_limit = val; + state->control.rate_limit_specified = was_specified; state->control.rate_limit_active = is_active; } From 0a98062586cae8e9e27270b834cbfc41c0fdb382 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Sat, 9 May 2026 00:35:19 +0100 Subject: [PATCH 4/5] Define RATE_BURST_WINDOW as a floating point value since it's used in floating point calculations (#193). --- src/include/pv-internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index 730e6c7..3cc57be 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -24,7 +24,7 @@ extern "C" { #endif #define RATE_GRANULARITY 100000000 /* nsec between -L rate chunks */ -#define RATE_BURST_WINDOW 5 /* rate burst window (multiples of rate) */ +#define RATE_BURST_WINDOW 5.0 /* rate burst window (multiples of rate) */ #define REMOTE_INTERVAL 100000000 /* nsec between checks for -R and -Q */ #define MONITOR_EXCHANGE_INTERVAL 100000000 /* nsec between "-M both" data exchanges */ #define BUFFER_SIZE (size_t) 409600 /* default transfer buffer size */ From d381a1c7143f603b15ff28310f7e31a08cbfadde Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Sat, 9 May 2026 00:37:29 +0100 Subject: [PATCH 5/5] Correct the rate limit burst cap so it won't prevent output when the rate is below 0.2, and handle the case where more than one rate limit interval has passed since the last calculation (#193). --- src/pv/loop.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/pv/loop.c b/src/pv/loop.c index 029d1f0..0785924 100644 --- a/src/pv/loop.c +++ b/src/pv/loop.c @@ -348,8 +348,8 @@ int pv_main_loop(pvstate_t state) { long lineswritten; off_t cansend; + long double rate_limited_target; ssize_t written; - long double target; bool eof_in, eof_out, final_update; struct timespec start_time, next_update, next_ratecheck, cur_time; struct timespec next_remotecheck, next_monitor_exchange; @@ -443,7 +443,7 @@ int pv_main_loop(pvstate_t state) pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->control.interval)); } - target = 0; + rate_limited_target = 0.0; final_update = false; file_idx = 0; @@ -574,18 +574,24 @@ int pv_main_loop(pvstate_t state) if (state->control.rate_limit_active) { pv_elapsedtime_read(&cur_time); - if (pv_elapsedtime_compare(&cur_time, &next_ratecheck) > 0) { - target += + while (pv_elapsedtime_compare(&cur_time, &next_ratecheck) > 0) { + rate_limited_target += ((long double) (state->control.rate_limit)) / (long double) (1000000000.0 / (long double) (RATE_GRANULARITY)); long double burst_max = ((long double) (state->control.rate_limit * RATE_BURST_WINDOW)); - if (target > burst_max) { - target = burst_max; + if (burst_max > 1.0 && rate_limited_target > burst_max) { + /* + * If the burst max is < 1 then + * capping to that will mean nothing + * ever gets sent, so turn off the + * burst limit at 1 and below. + */ + rate_limited_target = burst_max; } pv_elapsedtime_add_nsec(&next_ratecheck, RATE_GRANULARITY); } - cansend = (off_t) target; + cansend = (off_t) (rate_limited_target); } /* @@ -623,11 +629,11 @@ int pv_main_loop(pvstate_t state) if (state->control.linemode) { state->transfer.total_written += lineswritten; if (state->control.rate_limit_active) - target -= lineswritten; + rate_limited_target -= lineswritten; } else { state->transfer.total_written += written; if (state->control.rate_limit_active) - target -= written; + rate_limited_target -= written; } #ifdef FIONREAD