@ -383,38 +383,44 @@ fn emit_progress(tx: &watch::Sender<ProgressEvent>, step: &str, message: &str, p
/// Returns `None` if the user has no rate limit overrides, in which case the
/// Returns `None` if the user has no rate limit overrides, in which case the
/// global provider rate limiter should be used instead.
/// global provider rate limiter should be used instead.
///
///
/// The limiter is stored in `state.user_rate_limiters` keyed by `user_id` so that
/// Uses DashMap's entry API for atomic check-and-insert, preventing concurrent
/// rate limit history persists across generation jobs. If the user's settings have
/// generation jobs from creating independent limiters for the same user.
/// changed since the limiter was created, a fresh limiter replaces the old one.
fn get_user_rate_limiter (
fn get_user_rate_limiter (
state : & AppState ,
state : & AppState ,
settings : & UserSettings ,
settings : & UserSettings ,
user_id : Uuid ,
user_id : Uuid ,
) -> Option < crate ::services ::rate_limiter ::RateLimiter > {
) -> Option < crate ::services ::rate_limiter ::RateLimiter > {
use crate ::app_state ::UserRateLimitEntry ;
match (
match (
settings . rate_limit_max_requests ,
settings . rate_limit_max_requests ,
settings . rate_limit_time_window_seconds ,
settings . rate_limit_time_window_seconds ,
) {
) {
( Some ( max_req ) , Some ( window_sec ) ) = > {
( Some ( max_req ) , Some ( window_sec ) ) = > {
// Reuse existing limiter if settings haven't changed
let mut entry = state . user_rate_limiters . entry ( user_id ) . or_insert_with ( | | {
if let Some ( entry ) = state . user_rate_limiters . get ( & user_id ) {
UserRateLimitEntry {
let ( stored_max , stored_window , ref limiter ) = * entry ;
max_requests : max_req ,
if stored_max = = max_req & & stored_window = = window_sec {
window_seconds : window_sec ,
return Some ( limiter . clone ( ) ) ;
limiter : crate ::services ::rate_limiter ::RateLimiter ::new (
}
max_req as usize ,
Duration ::from_secs ( window_sec as u64 ) ,
) ,
}
}
// Settings changed or first generation — create and store
} ) ;
let limiter = crate ::services ::rate_limiter ::RateLimiter ::new (
// Replace if user's settings changed since the limiter was created
if entry . max_requests ! = max_req | | entry . window_seconds ! = window_sec {
* entry = UserRateLimitEntry {
max_requests : max_req ,
window_seconds : window_sec ,
limiter : crate ::services ::rate_limiter ::RateLimiter ::new (
max_req as usize ,
max_req as usize ,
Duration ::from_secs ( window_sec as u64 ) ,
Duration ::from_secs ( window_sec as u64 ) ,
) ;
) ,
state
} ;
. user_rate_limiters
}
. insert ( user_id , ( max_req , window_sec , limiter . clone ( ) ) ) ;
Some ( entry . limiter . clone ( ) )
Some ( limiter )
}
}
_ = > {
_ = > {
// User cleared their overrides — remove stale limiter
state . user_rate_limiters . remove ( & user_id ) ;
state . user_rate_limiters . remove ( & user_id ) ;
None
None
}
}