You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Smg RateLimiter Parameters Are Confusing (Possibly Bugs)
// ==================== Rate Limiting ====================/// Maximum concurrent requests (-1 to disable)#[arg(long, default_value_t = -1, help_heading = "Rate Limiting")]
max_concurrent_requests:i32,/// Queue size for pending requests when limit reached#[arg(long, default_value_t = 100, help_heading = "Rate Limiting")]
queue_size:usize,/// Maximum time in seconds a request can wait in queue#[arg(long, default_value_t = 60, help_heading = "Rate Limiting")]
queue_timeout_secs:u64,/// Token bucket refill rate (tokens per second)#[arg(long, help_heading = "Rate Limiting")]
rate_limit_tokens_per_second:Option<i32>,
max_concurrent_requests vs rate_limit_tokens_per_second
max_concurrent_requests appears to control concurrency. Based on the token bucket implementation, it should support two modes:
Semaphore mode: limits concurrency
Token bucket mode: limits request rate
implTokenBucket{/// Create a new token bucket////// # Arguments/// * `capacity` - Maximum number of tokens (burst capacity)/// * `refill_rate` - Tokens added per second (0 for pure concurrency limiting)pubfnnew(capacity:usize,refill_rate:usize) -> Self{let capacity = capacity asf64;// Allow refill_rate=0 for pure concurrency limiting (semaphore behavior)// When refill_rate=0, tokens are only returned via return_tokens()let refill_rate = refill_rate asf64;Self{inner:Arc::new(Mutex::new(TokenBucketInner{tokens: capacity,last_refill:Instant::now(),})),notify:Arc::new(Notify::new()),
capacity,
refill_rate,}}}
Semaphore behavior
fnmaybe_rate_limiter(mutself,config:&RouterConfig) -> Self{self.rate_limiter = match config.max_concurrent_requests{
n if n <= 0 => None,
n => {let rate_limit_tokens = config
.rate_limit_tokens_per_second.filter(|&t| t > 0).unwrap_or(n);Some(Arc::new(TokenBucket::new(
n asusize,
rate_limit_tokens asusize,)))}};self}
During initialization, if rate_limit_tokens_per_second = 0, it falls back to unwrap_or(n), so:
rate_limit_tokens_per_second = max_concurrent_requests
However, based on the token bucket implementation, setting refill_rate = 0 should enable pure semaphore behavior. This path is effectively unreachable. Is this intentional?
In a typical token bucket implementation, tokens limit the request input rate. However, returning tokens after request completion makes rate limiting impossible. If this were a semaphore, returning tokens would make sense—but why does a token bucket need this design? This makes max_concurrent_requests extremely confusing to understand.
queue_size
let(queue_tx, queue_rx) = mpsc::channel(size);
This suggests queue_size limits the number of queued requests.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Smg RateLimiter Parameters Are Confusing (Possibly Bugs)
max_concurrent_requests vs rate_limit_tokens_per_second
max_concurrent_requestsappears to control concurrency. Based on the token bucket implementation, it should support two modes:Semaphore behavior
During initialization, if
rate_limit_tokens_per_second = 0, it falls back tounwrap_or(n), so:rate_limit_tokens_per_second = max_concurrent_requests
However, based on the token bucket implementation, setting
refill_rate = 0should enable pure semaphore behavior. This path is effectively unreachable. Is this intentional?Token bucket behavior
In a typical token bucket implementation, tokens limit the request input rate. However, returning tokens after request completion makes rate limiting impossible. If this were a semaphore, returning tokens would make sense—but why does a token bucket need this design? This makes
max_concurrent_requestsextremely confusing to understand.queue_size
This suggests
queue_sizelimits the number of queued requests.However, in practice:
Queued requests are processed via
tokio::spawn, which allows unbounded async tasks. This seems to bypass the intended queue size limit.So
queue_sizedoes not actually cap the number of waiting requests. This behavior does not match the parameter's meaning.queue_timeout_secs
queue_timeout_secsis applied via:However, in the underlying implementation:
When
refill_rate > 0(which is always the case, since it cannot be set to 0), the wait time becomes: wait_time = tokens_needed / refill_rateThis causes
acquire()to return quickly, instead of waiting forqueue_timeout_secs.Only when
refill_rate = 0does this logic work as expected:But since
rate_limit_tokens_per_secondcannot be set to 0, this mode is effectively unusable.Token leakage when request is dropped
Currently, tokens are returned via
TokenGuardBody:However, in production we have observed cases where:
This leads to token leakage and eventual throttling issues.
Summary
In production (using smg + sglang), the rate limiter behavior is difficult to reason about and does not match expectations:
refill_rate = 0should enable semaphorerate_limit_tokens_per_secondcannot be 0queue_sizetokio::spawnallows unbounded async tasksqueue_timeout_secsrefill_rate > 0These issues appear more like bugs than intentional design.
Could you clarify whether this behavior is expected, or if there are known issues or planned fixes?
All reactions