1use std::collections::HashMap;
13use std::sync::Arc;
14use std::time::{Duration, Instant};
15
16use async_trait::async_trait;
17use chrono::Utc;
18use observe::{BrainEvent, Observer};
19use tokio::sync::{Mutex, RwLock};
20use tracing::{debug, info, warn};
21use uuid::Uuid;
22
23#[derive(Debug, Clone)]
27pub struct BreakerConfig {
28 pub failure_threshold: u32,
30 pub open_duration: Duration,
33 pub half_open_required_successes: u32,
36}
37
38impl Default for BreakerConfig {
39 fn default() -> Self {
40 Self {
41 failure_threshold: 3,
42 open_duration: Duration::from_secs(30),
43 half_open_required_successes: 1,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum BreakerState {
53 Closed,
54 Open { opened_at: Instant },
55 HalfOpen,
56}
57
58impl BreakerState {
59 pub fn as_str(&self) -> &'static str {
61 match self {
62 BreakerState::Closed => "closed",
63 BreakerState::Open { .. } => "open",
64 BreakerState::HalfOpen => "half_open",
65 }
66 }
67}
68
69struct Inner {
70 state: BreakerState,
71 consecutive_failures: u32,
73 probe_successes: u32,
75}
76
77#[async_trait]
91pub trait Breaker: Send + Sync {
92 fn tool_id(&self) -> &str;
95 async fn is_open(&self) -> bool;
98 async fn record_success(&self);
100 async fn record_failure(&self);
102}
103
104#[async_trait]
105impl<B: Breaker + ?Sized> Breaker for Arc<B> {
106 fn tool_id(&self) -> &str {
107 (**self).tool_id()
108 }
109 async fn is_open(&self) -> bool {
110 (**self).is_open().await
111 }
112 async fn record_success(&self) {
113 (**self).record_success().await
114 }
115 async fn record_failure(&self) {
116 (**self).record_failure().await
117 }
118}
119
120pub struct CircuitBreaker {
126 tool_id: String,
127 config: BreakerConfig,
128 inner: Mutex<Inner>,
129 observer: Option<Arc<dyn Observer>>,
130}
131
132impl CircuitBreaker {
133 pub fn new(tool_id: impl Into<String>, config: BreakerConfig) -> Self {
134 Self {
135 tool_id: tool_id.into(),
136 config,
137 inner: Mutex::new(Inner {
138 state: BreakerState::Closed,
139 consecutive_failures: 0,
140 probe_successes: 0,
141 }),
142 observer: None,
143 }
144 }
145
146 pub fn with_observer(mut self, observer: Arc<dyn Observer>) -> Self {
149 self.observer = Some(observer);
150 self
151 }
152
153 pub fn tool_id(&self) -> &str {
154 &self.tool_id
155 }
156
157 pub fn config(&self) -> &BreakerConfig {
158 &self.config
159 }
160
161 pub async fn state(&self) -> BreakerState {
164 self.inner.lock().await.state
165 }
166
167 pub async fn is_open(&self) -> bool {
172 let mut inner = self.inner.lock().await;
173 if let BreakerState::Open { opened_at } = inner.state {
174 if opened_at.elapsed() >= self.config.open_duration {
175 self.transition(&mut inner, BreakerState::HalfOpen).await;
176 return false;
177 }
178 return true;
179 }
180 false
181 }
182
183 pub async fn record_success(&self) {
184 let mut inner = self.inner.lock().await;
185 match inner.state {
186 BreakerState::Closed => {
187 inner.consecutive_failures = 0;
188 }
189 BreakerState::HalfOpen => {
190 inner.probe_successes += 1;
191 if inner.probe_successes >= self.config.half_open_required_successes {
192 self.transition(&mut inner, BreakerState::Closed).await;
193 }
194 }
195 BreakerState::Open { .. } => {
196 self.transition(&mut inner, BreakerState::HalfOpen).await;
201 inner.probe_successes = 1;
202 if inner.probe_successes >= self.config.half_open_required_successes {
203 self.transition(&mut inner, BreakerState::Closed).await;
204 }
205 }
206 }
207 }
208
209 pub async fn record_failure(&self) {
210 let mut inner = self.inner.lock().await;
211 match inner.state {
212 BreakerState::Closed => {
213 inner.consecutive_failures += 1;
214 if inner.consecutive_failures >= self.config.failure_threshold {
215 self.transition(
216 &mut inner,
217 BreakerState::Open {
218 opened_at: Instant::now(),
219 },
220 )
221 .await;
222 }
223 }
224 BreakerState::HalfOpen => {
225 self.transition(
226 &mut inner,
227 BreakerState::Open {
228 opened_at: Instant::now(),
229 },
230 )
231 .await;
232 }
233 BreakerState::Open { .. } => {
234 }
237 }
238 }
239
240 async fn transition(&self, inner: &mut Inner, to: BreakerState) {
241 let from = inner.state;
242 if from == to {
243 return;
244 }
245 inner.state = to;
246 match to {
247 BreakerState::Closed => {
248 inner.consecutive_failures = 0;
249 inner.probe_successes = 0;
250 info!(tool_id = %self.tool_id, "Circuit breaker closed");
251 }
252 BreakerState::Open { .. } => {
253 inner.probe_successes = 0;
254 warn!(
255 tool_id = %self.tool_id,
256 open_duration_secs = self.config.open_duration.as_secs(),
257 "Circuit breaker OPEN",
258 );
259 }
260 BreakerState::HalfOpen => {
261 inner.probe_successes = 0;
262 debug!(tool_id = %self.tool_id, "Circuit breaker entering HalfOpen probe");
263 }
264 }
265 if let Some(observer) = &self.observer {
266 let _ = observer
267 .publish(BrainEvent::BreakerStateChange {
268 id: Uuid::new_v4(),
269 tool_id: self.tool_id.clone(),
270 from: from.as_str().to_string(),
271 to: to.as_str().to_string(),
272 ts: Utc::now(),
273 })
274 .await;
275 }
276 }
277}
278
279#[async_trait]
283impl Breaker for CircuitBreaker {
284 fn tool_id(&self) -> &str {
285 CircuitBreaker::tool_id(self)
286 }
287 async fn is_open(&self) -> bool {
288 CircuitBreaker::is_open(self).await
289 }
290 async fn record_success(&self) {
291 CircuitBreaker::record_success(self).await
292 }
293 async fn record_failure(&self) {
294 CircuitBreaker::record_failure(self).await
295 }
296}
297
298pub struct BreakerRegistry {
304 breakers: RwLock<HashMap<String, Arc<CircuitBreaker>>>,
305 config: BreakerConfig,
306 observer: Option<Arc<dyn Observer>>,
307}
308
309impl BreakerRegistry {
310 pub fn new(config: BreakerConfig) -> Self {
314 Self {
315 breakers: RwLock::new(HashMap::new()),
316 config,
317 observer: None,
318 }
319 }
320
321 pub fn with_observer(mut self, observer: Arc<dyn Observer>) -> Self {
322 self.observer = Some(observer);
323 self
324 }
325
326 pub async fn get_or_create(&self, tool_id: &str) -> Arc<CircuitBreaker> {
328 if let Some(existing) = self.breakers.read().await.get(tool_id) {
329 return existing.clone();
330 }
331 let mut guard = self.breakers.write().await;
332 if let Some(existing) = guard.get(tool_id) {
334 return existing.clone();
335 }
336 let mut cb = CircuitBreaker::new(tool_id, self.config.clone());
337 if let Some(obs) = &self.observer {
338 cb = cb.with_observer(obs.clone());
339 }
340 let arc = Arc::new(cb);
341 guard.insert(tool_id.to_string(), arc.clone());
342 arc
343 }
344
345 pub async fn get(&self, tool_id: &str) -> Option<Arc<CircuitBreaker>> {
347 self.breakers.read().await.get(tool_id).cloned()
348 }
349
350 pub async fn record_success(&self, tool_id: &str) {
353 self.get_or_create(tool_id).await.record_success().await;
354 }
355
356 pub async fn record_failure(&self, tool_id: &str) {
359 self.get_or_create(tool_id).await.record_failure().await;
360 }
361
362 pub async fn len(&self) -> usize {
363 self.breakers.read().await.len()
364 }
365
366 pub async fn is_empty(&self) -> bool {
367 self.breakers.read().await.is_empty()
368 }
369}
370
371#[async_trait]
372impl intent::BreakerCheck for BreakerRegistry {
373 async fn is_open(&self, tool_id: &str) -> bool {
374 match self.get(tool_id).await {
377 Some(cb) => cb.is_open().await,
378 None => false,
379 }
380 }
381}
382
383mod retry;
386pub use retry::{retry, BreakerOpenAbort, RetryConfig, RetryOutcome};
387
388mod timeout;
391pub use timeout::{timeout, TimeoutError};
392
393mod rate_limit;
396pub use rate_limit::{RateLimitConfig, RateLimitRegistry, RateLimiter};
397
398mod loop_detector;
401pub use loop_detector::{LoopDetector, LoopDetectorConfig, LoopDetectorError};
402
403mod dlq;
406pub use dlq::{DeadLetterQueue, DlqEntry, DlqError, InMemoryDlq};
407
408#[cfg(test)]
409mod tests;