From: Christian Ebner <c.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox v2 2/4] http: drop factored out rate limiter implementation
Date: Tue, 16 Sep 2025 14:41:41 +0200 [thread overview]
Message-ID: <20250916124147.513342-3-c.ebner@proxmox.com> (raw)
In-Reply-To: <20250916124147.513342-1-c.ebner@proxmox.com>
The rate limiter implementation has been moved together with the shared
rate limiter into a dedicated crate. Depend on that and drop the now
dead code from the proxmox-http crate.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
Changes since version 1:
- not present in previous version
Cargo.toml | 1 +
proxmox-http/Cargo.toml | 4 +-
proxmox-http/debian/control | 19 +--
proxmox-http/src/client/connector.rs | 3 +-
proxmox-http/src/lib.rs | 5 -
proxmox-http/src/rate_limited_stream.rs | 2 +-
proxmox-http/src/rate_limiter.rs | 214 ------------------------
7 files changed, 8 insertions(+), 240 deletions(-)
delete mode 100644 proxmox-http/src/rate_limiter.rs
diff --git a/Cargo.toml b/Cargo.toml
index bde32b17..f62b9882 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -153,6 +153,7 @@ proxmox-login = { version = "1.0.0", path = "proxmox-login" }
proxmox-network-types = { version = "0.1.0", path = "proxmox-network-types" }
proxmox-product-config = { version = "1.0.0", path = "proxmox-product-config" }
proxmox-config-digest = { version = "1.0.0", path = "proxmox-config-digest" }
+proxmox-rate-limiter = { version = "1.0.0", path = "proxmox-rate-limiter" }
proxmox-rest-server = { version = "1.0.0", path = "proxmox-rest-server" }
proxmox-router = { version = "3.2.2", path = "proxmox-router" }
proxmox-s3-client = { version = "1.2", path = "proxmox-s3-client" }
diff --git a/proxmox-http/Cargo.toml b/proxmox-http/Cargo.toml
index 2f83cf5f..0b362bef 100644
--- a/proxmox-http/Cargo.toml
+++ b/proxmox-http/Cargo.toml
@@ -32,6 +32,7 @@ url = { workspace = true, optional = true }
proxmox-async = { workspace = true, optional = true }
proxmox-base64 = { workspace = true, optional = true }
+proxmox-rate-limiter = { workspace = true, optional = true, features = [ "rate-limiter" ] }
proxmox-sys = { workspace = true, optional = true }
proxmox-io = { workspace = true, optional = true }
proxmox-lang = { workspace = true, optional = true }
@@ -53,7 +54,6 @@ body = [
"dep:sync_wrapper",
"sync_wrapper?/futures",
]
-rate-limiter = ["dep:hyper"]
rate-limited-stream = [
"dep:tokio",
"dep:hyper-util",
@@ -61,7 +61,7 @@ rate-limited-stream = [
"hyper-util?/client-legacy",
"hyper-util?/http1",
"tokio?/time",
- "rate-limiter",
+ "dep:proxmox-rate-limiter",
]
client = [
"dep:bytes",
diff --git a/proxmox-http/debian/control b/proxmox-http/debian/control
index f86e58d1..ae3af111 100644
--- a/proxmox-http/debian/control
+++ b/proxmox-http/debian/control
@@ -29,7 +29,6 @@ Suggests:
librust-proxmox-http+http-helpers-dev (= ${binary:Version}),
librust-proxmox-http+proxmox-async-dev (= ${binary:Version}),
librust-proxmox-http+rate-limited-stream-dev (= ${binary:Version}),
- librust-proxmox-http+rate-limiter-dev (= ${binary:Version}),
librust-proxmox-http+websocket-dev (= ${binary:Version})
Provides:
librust-proxmox-http+default-dev (= ${binary:Version}),
@@ -172,12 +171,13 @@ Multi-Arch: same
Depends:
${misc:Depends},
librust-proxmox-http-dev (= ${binary:Version}),
- librust-proxmox-http+rate-limiter-dev (= ${binary:Version}),
librust-hyper-util-0.1+client-dev (>= 0.1.12-~~),
librust-hyper-util-0.1+client-legacy-dev (>= 0.1.12-~~),
librust-hyper-util-0.1+default-dev (>= 0.1.12-~~),
librust-hyper-util-0.1+http1-dev (>= 0.1.12-~~),
librust-hyper-util-0.1+http2-dev (>= 0.1.12-~~),
+ librust-proxmox-rate-limiter-1+default-dev,
+ librust-proxmox-rate-limiter-1+rate-limiter-dev,
librust-tokio-1+default-dev (>= 1.6-~~),
librust-tokio-1+time-dev (>= 1.6-~~)
Provides:
@@ -188,21 +188,6 @@ Description: Proxmox HTTP library - feature "rate-limited-stream"
This metapackage enables feature "rate-limited-stream" for the Rust proxmox-
http crate, by pulling in any additional dependencies needed by that feature.
-Package: librust-proxmox-http+rate-limiter-dev
-Architecture: any
-Multi-Arch: same
-Depends:
- ${misc:Depends},
- librust-proxmox-http-dev (= ${binary:Version}),
- librust-hyper-1+default-dev
-Provides:
- librust-proxmox-http-1+rate-limiter-dev (= ${binary:Version}),
- librust-proxmox-http-1.0+rate-limiter-dev (= ${binary:Version}),
- librust-proxmox-http-1.0.3+rate-limiter-dev (= ${binary:Version})
-Description: Proxmox HTTP library - feature "rate-limiter"
- This metapackage enables feature "rate-limiter" for the Rust proxmox-http
- crate, by pulling in any additional dependencies needed by that feature.
-
Package: librust-proxmox-http+websocket-dev
Architecture: any
Multi-Arch: same
diff --git a/proxmox-http/src/client/connector.rs b/proxmox-http/src/client/connector.rs
index 1600d47c..d5d85cb9 100644
--- a/proxmox-http/src/client/connector.rs
+++ b/proxmox-http/src/client/connector.rs
@@ -13,13 +13,14 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio_openssl::SslStream;
+use proxmox_rate_limiter::ShareableRateLimit;
use proxmox_sys::linux::socket::set_tcp_keepalive;
use crate::proxy_config::ProxyConfig;
use crate::uri::build_authority;
use super::tls::MaybeTlsStream;
-use crate::{RateLimitedStream, ShareableRateLimit};
+use crate::RateLimitedStream;
type SharedRateLimit = Arc<dyn ShareableRateLimit>;
diff --git a/proxmox-http/src/lib.rs b/proxmox-http/src/lib.rs
index 8b6953b0..7f6f67f8 100644
--- a/proxmox-http/src/lib.rs
+++ b/proxmox-http/src/lib.rs
@@ -26,11 +26,6 @@ mod client_trait;
#[cfg(feature = "client-trait")]
pub use client_trait::HttpClient;
-#[cfg(feature = "rate-limiter")]
-mod rate_limiter;
-#[cfg(feature = "rate-limiter")]
-pub use rate_limiter::{RateLimit, RateLimiter, RateLimiterVec, ShareableRateLimit};
-
#[cfg(feature = "rate-limited-stream")]
mod rate_limited_stream;
#[cfg(feature = "rate-limited-stream")]
diff --git a/proxmox-http/src/rate_limited_stream.rs b/proxmox-http/src/rate_limited_stream.rs
index e9308a47..2253bef8 100644
--- a/proxmox-http/src/rate_limited_stream.rs
+++ b/proxmox-http/src/rate_limited_stream.rs
@@ -11,7 +11,7 @@ use tokio::time::Sleep;
use std::task::{Context, Poll};
-use super::{RateLimiter, ShareableRateLimit};
+use proxmox_rate_limiter::{RateLimiter, ShareableRateLimit};
type SharedRateLimit = Arc<dyn ShareableRateLimit>;
diff --git a/proxmox-http/src/rate_limiter.rs b/proxmox-http/src/rate_limiter.rs
deleted file mode 100644
index 945c77a6..00000000
--- a/proxmox-http/src/rate_limiter.rs
+++ /dev/null
@@ -1,214 +0,0 @@
-use std::convert::TryInto;
-use std::time::{Duration, Instant};
-
-use anyhow::{bail, Error};
-
-/// Rate limiter interface.
-pub trait RateLimit {
- /// Update rate and bucket size
- fn update_rate(&mut self, rate: u64, bucket_size: u64);
-
- /// Returns the overall traffic (since started)
- fn traffic(&self) -> u64;
-
- /// Register traffic, returning a proposed delay to reach the
- /// expected rate.
- fn register_traffic(&mut self, current_time: Instant, data_len: u64) -> Duration;
-}
-
-/// Like [`RateLimit`], but does not require self to be mutable.
-///
-/// This is useful for types providing internal mutability (Mutex).
-pub trait ShareableRateLimit: Send + Sync {
- fn update_rate(&self, rate: u64, bucket_size: u64);
- fn traffic(&self) -> u64;
- fn register_traffic(&self, current_time: Instant, data_len: u64) -> Duration;
-}
-
-/// IMPORTANT: We use this struct in shared memory, so please do not
-/// change/modify the layout (do not add fields)
-#[derive(Clone)]
-#[repr(C)]
-struct TbfState {
- traffic: u64, // overall traffic
- last_update: Instant,
- consumed_tokens: u64,
-}
-
-impl TbfState {
- const NO_DELAY: Duration = Duration::from_millis(0);
-
- fn refill_bucket(&mut self, rate: u64, current_time: Instant) {
- let time_diff = match current_time.checked_duration_since(self.last_update) {
- Some(duration) => duration.as_nanos(),
- None => return,
- };
-
- if time_diff == 0 {
- return;
- }
-
- self.last_update = current_time;
-
- let allowed_traffic = ((time_diff.saturating_mul(rate as u128)) / 1_000_000_000)
- .try_into()
- .unwrap_or(u64::MAX);
-
- self.consumed_tokens = self.consumed_tokens.saturating_sub(allowed_traffic);
- }
-
- fn register_traffic(
- &mut self,
- rate: u64,
- bucket_size: u64,
- current_time: Instant,
- data_len: u64,
- ) -> Duration {
- self.refill_bucket(rate, current_time);
-
- self.traffic += data_len;
- self.consumed_tokens += data_len;
-
- if self.consumed_tokens <= bucket_size {
- return Self::NO_DELAY;
- }
- Duration::from_nanos(
- (self.consumed_tokens - bucket_size).saturating_mul(1_000_000_000) / rate,
- )
- }
-}
-
-/// Token bucket based rate limiter
-///
-/// IMPORTANT: We use this struct in shared memory, so please do not
-/// change/modify the layout (do not add fields)
-#[repr(C)]
-pub struct RateLimiter {
- rate: u64, // tokens/second
- bucket_size: u64, // TBF bucket size
- state: TbfState,
-}
-
-impl RateLimiter {
- /// Creates a new instance, using [Instant::now] as start time.
- pub fn new(rate: u64, bucket_size: u64) -> Self {
- let start_time = Instant::now();
- Self::with_start_time(rate, bucket_size, start_time)
- }
-
- /// Creates a new instance with specified `rate`, `bucket_size` and `start_time`.
- pub fn with_start_time(rate: u64, bucket_size: u64, start_time: Instant) -> Self {
- Self {
- rate,
- bucket_size,
- state: TbfState {
- traffic: 0,
- last_update: start_time,
- // start with empty bucket (all tokens consumed)
- consumed_tokens: bucket_size,
- },
- }
- }
-}
-
-impl RateLimit for RateLimiter {
- fn update_rate(&mut self, rate: u64, bucket_size: u64) {
- self.rate = rate;
-
- if bucket_size < self.bucket_size && self.state.consumed_tokens > bucket_size {
- self.state.consumed_tokens = bucket_size; // start again
- }
-
- self.bucket_size = bucket_size;
- }
-
- fn traffic(&self) -> u64 {
- self.state.traffic
- }
-
- fn register_traffic(&mut self, current_time: Instant, data_len: u64) -> Duration {
- self.state
- .register_traffic(self.rate, self.bucket_size, current_time, data_len)
- }
-}
-
-impl<R: RateLimit + Send> ShareableRateLimit for std::sync::Mutex<R> {
- fn update_rate(&self, rate: u64, bucket_size: u64) {
- self.lock().unwrap().update_rate(rate, bucket_size);
- }
-
- fn traffic(&self) -> u64 {
- self.lock().unwrap().traffic()
- }
-
- fn register_traffic(&self, current_time: Instant, data_len: u64) -> Duration {
- self.lock()
- .unwrap()
- .register_traffic(current_time, data_len)
- }
-}
-
-/// Array of rate limiters.
-///
-/// A group of rate limiters with same configuration.
-pub struct RateLimiterVec {
- rate: u64, // tokens/second
- bucket_size: u64, // TBF bucket size
- state: Vec<TbfState>,
-}
-
-impl RateLimiterVec {
- /// Creates a new instance, using [Instant::now] as start time.
- pub fn new(group_size: usize, rate: u64, bucket_size: u64) -> Self {
- let start_time = Instant::now();
- Self::with_start_time(group_size, rate, bucket_size, start_time)
- }
-
- /// Creates a new instance with specified `rate`, `bucket_size` and `start_time`.
- pub fn with_start_time(
- group_size: usize,
- rate: u64,
- bucket_size: u64,
- start_time: Instant,
- ) -> Self {
- let state = TbfState {
- traffic: 0,
- last_update: start_time,
- // start with empty bucket (all tokens consumed)
- consumed_tokens: bucket_size,
- };
- Self {
- rate,
- bucket_size,
- state: vec![state; group_size],
- }
- }
-
- #[allow(clippy::len_without_is_empty)]
- /// Return the number of TBF entries (group_size)
- pub fn len(&self) -> usize {
- self.state.len()
- }
-
- /// Traffic for the specified index
- pub fn traffic(&self, index: usize) -> Result<u64, Error> {
- if index >= self.state.len() {
- bail!("RateLimiterVec::traffic - index out of range");
- }
- Ok(self.state[index].traffic)
- }
-
- /// Register traffic at the specified index
- pub fn register_traffic(
- &mut self,
- index: usize,
- current_time: Instant,
- data_len: u64,
- ) -> Result<Duration, Error> {
- if index >= self.state.len() {
- bail!("RateLimiterVec::register_traffic - index out of range");
- }
-
- Ok(self.state[index].register_traffic(self.rate, self.bucket_size, current_time, data_len))
- }
-}
--
2.47.3
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
next prev parent reply other threads:[~2025-09-16 12:42 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-16 12:41 [pbs-devel] [PATCH proxmox{, -backup} v2 0/8] shared rate limiter for s3 client instances Christian Ebner
2025-09-16 12:41 ` [pbs-devel] [PATCH proxmox v2 1/4] rate-limiter: add crate for traffic rate limiter implementations Christian Ebner
2025-09-16 12:41 ` Christian Ebner [this message]
2025-09-16 12:41 ` [pbs-devel] [PATCH proxmox v2 3/4] rest-server: optionally depend on factored out shared rate limiter Christian Ebner
2025-09-16 12:41 ` [pbs-devel] [PATCH proxmox v2 4/4] s3-client: add shared rate limiter via https connector Christian Ebner
2025-09-16 12:41 ` [pbs-devel] [PATCH proxmox-backup v2 1/4] traffic control: use factored out shared rate limiter Christian Ebner
2025-09-16 12:41 ` [pbs-devel] [PATCH proxmox-backup v2 2/4] api: config: update s3 endpoint rate limits in config Christian Ebner
2025-09-16 12:41 ` [pbs-devel] [PATCH proxmox-backup v2 3/4] datastore: s3: set rate limiter options for s3 client Christian Ebner
2025-09-16 12:41 ` [pbs-devel] [PATCH proxmox-backup v2 4/4] ui: expose rate and burst limits for s3 endpoints Christian Ebner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250916124147.513342-3-c.ebner@proxmox.com \
--to=c.ebner@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.