From 3669a7b1cdc75cec9f15077e08ff1ffaa17f789b Mon Sep 17 00:00:00 2001 From: alexthebuildr <116134064+ztsalexey@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:17:27 -0700 Subject: [PATCH] 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 --- src/llm/failover.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/llm/failover.rs b/src/llm/failover.rs index c53d725f..ae57dc33 100644 --- a/src/llm/failover.rs +++ b/src/llm/failover.rs @@ -105,8 +105,9 @@ impl ProviderCooldown { /// Activate cooldown at the given timestamp. fn activate_cooldown(&self, now_nanos: u64) { + // Ensure 0 remains a safe "not in cooldown" sentinel. self.cooldown_activated_nanos - .store(now_nanos, Ordering::Relaxed); + .store(now_nanos.max(1), Ordering::Relaxed); } /// Reset failure count and clear cooldown (called on success). @@ -1041,6 +1042,15 @@ mod tests { assert!(result.is_err()); } + // Test: activate_cooldown(0) still activates cooldown (sentinel collision fix). + #[test] + fn cooldown_at_nanos_zero_still_activates() { + let cd = ProviderCooldown::new(); + cd.activate_cooldown(0); + assert!(cd.is_in_cooldown(0, 1000)); + assert_eq!(cd.cooldown_activated_nanos.load(Ordering::Relaxed), 1); + } + // Test: set_model propagates to all providers and active_model_name reflects change. #[test] fn set_model_propagates_to_all_providers() {