lean_ctx/core/context_kernel/
bounded.rs1use std::collections::VecDeque;
4use std::time::{Duration, Instant};
5
6#[derive(Debug, Clone)]
8pub struct BoundedQueue<T> {
9 items: VecDeque<T>,
10 max_size: usize,
11}
12
13impl<T> BoundedQueue<T> {
14 pub fn new(max_size: usize) -> Self {
16 Self {
17 items: VecDeque::with_capacity(max_size),
18 max_size,
19 }
20 }
21
22 pub fn push(&mut self, item: T) -> Option<T> {
24 if self.max_size == 0 {
25 return Some(item);
26 }
27
28 let evicted = if self.is_full() {
29 self.items.pop_front()
30 } else {
31 None
32 };
33 self.items.push_back(item);
34 evicted
35 }
36
37 pub fn len(&self) -> usize {
39 self.items.len()
40 }
41
42 pub fn is_empty(&self) -> bool {
44 self.items.is_empty()
45 }
46
47 pub fn is_full(&self) -> bool {
49 self.items.len() >= self.max_size
50 }
51
52 pub fn iter(&self) -> impl Iterator<Item = &T> {
54 self.items.iter()
55 }
56
57 pub fn drain_oldest(&mut self, n: usize) -> Vec<T> {
59 let count = n.min(self.items.len());
60 self.items.drain(..count).collect()
61 }
62
63 pub fn clear(&mut self) {
65 self.items.clear();
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum CircuitState {
72 Closed,
74 Open,
76 HalfOpen,
78}
79
80#[derive(Debug, Clone)]
82pub struct CircuitBreaker {
83 state: CircuitState,
84 failure_count: u32,
85 success_count: u32,
86 failure_threshold: u32,
87 recovery_threshold: u32,
88 last_state_change: Instant,
89 cooldown: Duration,
90}
91
92impl CircuitBreaker {
93 pub fn new(failure_threshold: u32, recovery_threshold: u32, cooldown: Duration) -> Self {
95 Self {
96 state: CircuitState::Closed,
97 failure_count: 0,
98 success_count: 0,
99 failure_threshold,
100 recovery_threshold,
101 last_state_change: Instant::now(),
102 cooldown,
103 }
104 }
105
106 pub fn default_breaker() -> Self {
108 Self::new(3, 2, Duration::from_secs(30))
109 }
110
111 pub fn state(&self) -> CircuitState {
113 self.state
114 }
115
116 pub fn record_success(&mut self) {
118 match self.state {
119 CircuitState::Closed => {
120 self.failure_count = 0;
121 }
122 CircuitState::HalfOpen => {
123 self.success_count = self.success_count.saturating_add(1);
124 if self.success_count >= self.recovery_threshold {
125 self.transition_to(CircuitState::Closed);
126 }
127 }
128 CircuitState::Open => {}
129 }
130 }
131
132 pub fn record_failure(&mut self) {
134 match self.state {
135 CircuitState::Closed => {
136 self.failure_count = self.failure_count.saturating_add(1);
137 if self.failure_count >= self.failure_threshold {
138 self.transition_to(CircuitState::Open);
139 }
140 }
141 CircuitState::HalfOpen => self.transition_to(CircuitState::Open),
142 CircuitState::Open => {}
143 }
144 }
145
146 pub fn should_allow(&mut self) -> bool {
151 match self.state {
152 CircuitState::Closed | CircuitState::HalfOpen => true,
153 CircuitState::Open => {
154 if self.last_state_change.elapsed() >= self.cooldown {
155 self.transition_to(CircuitState::HalfOpen);
156 true
157 } else {
158 false
159 }
160 }
161 }
162 }
163
164 pub fn reset(&mut self) {
166 self.transition_to(CircuitState::Closed);
167 }
168
169 fn transition_to(&mut self, state: CircuitState) {
170 self.state = state;
171 self.failure_count = 0;
172 self.success_count = 0;
173 self.last_state_change = Instant::now();
174 }
175}
176
177#[derive(Debug, Clone)]
179pub struct ProviderCircuit {
180 pub provider_id: String,
181 pub breaker: CircuitBreaker,
182 pub total_calls: u64,
183 pub total_failures: u64,
184}
185
186impl ProviderCircuit {
187 pub fn new(provider_id: String) -> Self {
189 Self {
190 provider_id,
191 breaker: CircuitBreaker::default_breaker(),
192 total_calls: 0,
193 total_failures: 0,
194 }
195 }
196
197 pub fn is_available(&mut self) -> bool {
199 self.breaker.should_allow()
200 }
201
202 pub fn record_outcome(&mut self, success: bool) {
204 self.total_calls = self.total_calls.saturating_add(1);
205 if success {
206 self.breaker.record_success();
207 } else {
208 self.total_failures = self.total_failures.saturating_add(1);
209 self.breaker.record_failure();
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::{BoundedQueue, CircuitBreaker, CircuitState, ProviderCircuit};
217 use std::time::Duration;
218
219 #[test]
220 fn bounded_queue_evicts_oldest() {
221 let mut queue = BoundedQueue::new(2);
222
223 assert_eq!(queue.push(1), None);
224 assert_eq!(queue.push(2), None);
225 assert_eq!(queue.push(3), Some(1));
226 assert_eq!(queue.iter().copied().collect::<Vec<i32>>(), vec![2, 3]);
227 assert!(queue.is_full());
228 }
229
230 #[test]
231 fn bounded_queue_drain() {
232 let mut queue = BoundedQueue::new(4);
233 queue.push("first");
234 queue.push("second");
235 queue.push("third");
236
237 assert_eq!(queue.drain_oldest(2), vec!["first", "second"]);
238 assert_eq!(queue.len(), 1);
239 assert_eq!(queue.drain_oldest(5), vec!["third"]);
240 assert!(queue.is_empty());
241 }
242
243 #[test]
244 fn bounded_queue_with_zero_capacity_rejects_items() {
245 let mut queue = BoundedQueue::new(0);
246
247 assert_eq!(queue.push(7), Some(7));
248 assert!(queue.is_empty());
249 assert!(queue.is_full());
250 }
251
252 #[test]
253 fn circuit_breaker_opens_after_threshold() {
254 let mut breaker = CircuitBreaker::new(3, 2, Duration::from_secs(30));
255
256 breaker.record_failure();
257 breaker.record_failure();
258 assert_eq!(breaker.state(), CircuitState::Closed);
259 breaker.record_failure();
260
261 assert_eq!(breaker.state(), CircuitState::Open);
262 assert!(!breaker.should_allow());
263 }
264
265 #[test]
266 fn circuit_breaker_recovers_via_half_open() {
267 let mut breaker = CircuitBreaker::new(1, 1, Duration::ZERO);
268 breaker.record_failure();
269
270 assert!(breaker.should_allow());
271 assert_eq!(breaker.state(), CircuitState::HalfOpen);
272 breaker.record_success();
273
274 assert_eq!(breaker.state(), CircuitState::Closed);
275 assert!(breaker.should_allow());
276 }
277
278 #[test]
279 fn provider_circuit_tracks_stats() {
280 let mut circuit = ProviderCircuit::new("filesystem".to_string());
281
282 circuit.record_outcome(true);
283 circuit.record_outcome(false);
284 circuit.record_outcome(false);
285
286 assert_eq!(circuit.total_calls, 3);
287 assert_eq!(circuit.total_failures, 2);
288 assert_eq!(circuit.breaker.state(), CircuitState::Closed);
289 }
290}