From c8729d38bcec065b72a790819cec830e3e2670dc Mon Sep 17 00:00:00 2001 From: zhenyi <434836402@qq.com> Date: Wed, 10 Jun 2026 18:32:16 +0800 Subject: [PATCH] fix(rate-limit): avoid over-admission when updating max_concurrent Only replace semaphores that have no active permits (idle repos). Previously all semaphores were replaced, allowing active permits from old semaphores plus full permits from new ones simultaneously. --- rate_limit.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/rate_limit.rs b/rate_limit.rs index 1834e59..04591ad 100644 --- a/rate_limit.rs +++ b/rate_limit.rs @@ -157,27 +157,38 @@ pub fn remove_repository(repo_relative_path: &str) { } /// Update the max concurrent limit at runtime. -/// This properly updates the limit and recreates all existing semaphores. +/// +/// Only replaces semaphores that have no active permits (idle repos). +/// Semaphores with in-flight operations are left untouched to avoid +/// a race where active permits are held against a now-replaced semaphore +/// while the new one grants a full set of permits — leading to over-admission. +/// Those repos will pick up the new limit once all permits are returned. pub fn set_max_concurrent(max: usize) { let l = limiter(); - // Update the max_concurrent value + let old_max = get_max_concurrent(); + match l.max_concurrent.write() { Ok(mut guard) => { *guard = max; } Err(e) => { - // Poisoned lock - recover and update let mut guard = e.into_inner(); *guard = max; } } - // Recreate all existing semaphores with the new limit let keys: Vec = l .semaphores .iter() - .map(|entry| entry.key().clone()) + .filter_map(|entry| { + let sem = entry.value(); + if sem.available_permits() == old_max { + Some(entry.key().clone()) + } else { + None + } + }) .collect(); for key in keys {