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