Skip to content

Commit 3669a7b

Browse files
ztsalexeyclaude
andauthored
fix: sentinel value collision in FailoverProvider cooldown (#125) (#154)
ProviderCooldown used 0 as both the "not in cooldown" sentinel and a valid timestamp from now_nanos(), so activate_cooldown(0) would silently fail to activate. Store max(now_nanos, 1) to keep 0 reserved. Closes #125 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 96d5fc0 commit 3669a7b

1 file changed

Lines changed: 11 additions & 1 deletion

File tree

src/llm/failover.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,9 @@ impl ProviderCooldown {
105105

106106
/// Activate cooldown at the given timestamp.
107107
fn activate_cooldown(&self, now_nanos: u64) {
108+
// Ensure 0 remains a safe "not in cooldown" sentinel.
108109
self.cooldown_activated_nanos
109-
.store(now_nanos, Ordering::Relaxed);
110+
.store(now_nanos.max(1), Ordering::Relaxed);
110111
}
111112

112113
/// Reset failure count and clear cooldown (called on success).
@@ -1041,6 +1042,15 @@ mod tests {
10411042
assert!(result.is_err());
10421043
}
10431044

1045+
// Test: activate_cooldown(0) still activates cooldown (sentinel collision fix).
1046+
#[test]
1047+
fn cooldown_at_nanos_zero_still_activates() {
1048+
let cd = ProviderCooldown::new();
1049+
cd.activate_cooldown(0);
1050+
assert!(cd.is_in_cooldown(0, 1000));
1051+
assert_eq!(cd.cooldown_activated_nanos.load(Ordering::Relaxed), 1);
1052+
}
1053+
10441054
// Test: set_model propagates to all providers and active_model_name reflects change.
10451055
#[test]
10461056
fn set_model_propagates_to_all_providers() {

0 commit comments

Comments
 (0)