tower_resilience_chaos/lib.rs
1//! Chaos engineering layer for Tower services.
2//!
3//! This crate provides a chaos engineering layer that can inject failures and latency
4//! into Tower services for testing resilience patterns. It's designed to help you verify
5//! that your circuit breakers, retries, timeouts, and other resilience mechanisms work
6//! correctly under adverse conditions.
7//!
8//! # Features
9//!
10//! - **Error Injection**: Inject errors at a configurable rate
11//! - **Latency Injection**: Add random delays to requests
12//! - **Deterministic Testing**: Use seeds for reproducible chaos
13//! - **Event System**: Monitor chaos injection via event listeners
14//! - **Composable**: Works with all other tower-resilience patterns
15//!
16//! # Safety
17//!
18//! **WARNING**: This layer is intended for testing and development only. Never use it in
19//! production environments. Consider using feature flags or environment checks to ensure
20//! chaos layers are only enabled in non-production environments.
21//!
22//! # Basic Example
23//!
24//! ## Latency-Only Chaos (no type parameters needed!)
25//!
26//! ```rust
27//! use tower::ServiceBuilder;
28//! use tower_resilience_chaos::ChaosLayer;
29//! use std::time::Duration;
30//!
31//! # async fn example() {
32//! // No type parameters required for latency-only chaos!
33//! let chaos = ChaosLayer::builder()
34//! .name("api-chaos")
35//! .latency_rate(0.2) // 20% of requests delayed
36//! .min_latency(Duration::from_millis(50))
37//! .max_latency(Duration::from_millis(200))
38//! .build();
39//!
40//! let service = ServiceBuilder::new()
41//! .layer(chaos)
42//! .service_fn(|req: String| async move {
43//! Ok::<String, std::io::Error>(format!("Response to: {}", req))
44//! });
45//! # }
46//! ```
47//!
48//! ## Error Injection (types inferred from closure)
49//!
50//! ```rust
51//! use tower::ServiceBuilder;
52//! use tower_resilience_chaos::ChaosLayer;
53//! use std::time::Duration;
54//!
55//! # async fn example() {
56//! // Types inferred from the error_fn closure signature
57//! let chaos = ChaosLayer::builder()
58//! .name("api-chaos")
59//! .error_rate(0.1) // 10% of requests fail
60//! .error_fn(|_req: &String| {
61//! std::io::Error::new(std::io::ErrorKind::Other, "chaos error!")
62//! })
63//! .latency_rate(0.2) // 20% of remaining requests delayed
64//! .min_latency(Duration::from_millis(50))
65//! .max_latency(Duration::from_millis(200))
66//! .build();
67//!
68//! let service = ServiceBuilder::new()
69//! .layer(chaos)
70//! .service_fn(|req: String| async move {
71//! Ok::<String, std::io::Error>(format!("Response to: {}", req))
72//! });
73//! # }
74//! ```
75//!
76//! # Testing Circuit Breakers
77//!
78//! ```rust,ignore
79//! use tower::ServiceBuilder;
80//! use tower_resilience_chaos::ChaosLayer;
81//! use tower_resilience_circuitbreaker::CircuitBreakerLayer;
82//! use std::time::Duration;
83//!
84//! # async fn example() {
85//! // Create a chaos layer that fails 60% of requests
86//! // Types inferred from closure signature
87//! let chaos = ChaosLayer::builder()
88//! .name("circuit-breaker-test")
89//! .error_rate(0.6)
90//! .error_fn(|_req: &String| {
91//! std::io::Error::new(std::io::ErrorKind::Other, "simulated failure")
92//! })
93//! .build();
94//!
95//! // Wrap with a circuit breaker
96//! let circuit_breaker = CircuitBreakerLayer::builder()
97//! .name("test-breaker")
98//! .failure_rate_threshold(0.5)
99//! .sliding_window_size(10)
100//! .build();
101//!
102//! let service = ServiceBuilder::new()
103//! .layer(circuit_breaker)
104//! .layer(chaos)
105//! .service_fn(|req: String| async move {
106//! Ok::<String, std::io::Error>(req)
107//! });
108//!
109//! // Make requests - circuit breaker should open after ~5 failures
110//! # }
111//! ```
112//!
113//! # Deterministic Testing
114//!
115//! Use a seed for reproducible chaos injection:
116//!
117//! ```rust
118//! use tower_resilience_chaos::ChaosLayer;
119//!
120//! # async fn example() {
121//! let chaos = ChaosLayer::builder()
122//! .error_rate(0.5)
123//! .error_fn(|_req: &()| {
124//! std::io::Error::new(std::io::ErrorKind::Other, "chaos")
125//! })
126//! .seed(42) // Same seed = same sequence of failures
127//! .build();
128//!
129//! // Running the same test multiple times will produce the same results
130//! # }
131//! ```
132//!
133//! # Event Monitoring
134//!
135//! Track chaos injection with event listeners:
136//!
137//! ```rust
138//! use tower_resilience_chaos::ChaosLayer;
139//! use std::sync::atomic::{AtomicUsize, Ordering};
140//! use std::sync::Arc;
141//! use std::time::Duration;
142//!
143//! # async fn example() {
144//! let errors = Arc::new(AtomicUsize::new(0));
145//! let latencies = Arc::new(AtomicUsize::new(0));
146//!
147//! let e = errors.clone();
148//! let l = latencies.clone();
149//!
150//! let chaos = ChaosLayer::builder()
151//! .error_rate(0.1)
152//! .error_fn(|_req: &()| {
153//! std::io::Error::new(std::io::ErrorKind::Other, "chaos")
154//! })
155//! .latency_rate(0.2)
156//! .on_error_injected(move || {
157//! e.fetch_add(1, Ordering::SeqCst);
158//! })
159//! .on_latency_injected(move |delay: Duration| {
160//! l.fetch_add(1, Ordering::SeqCst);
161//! })
162//! .build();
163//!
164//! // After running tests, check counters
165//! println!("Errors injected: {}", errors.load(Ordering::SeqCst));
166//! println!("Latencies injected: {}", latencies.load(Ordering::SeqCst));
167//! # }
168//! ```
169//!
170//! # Latency Injection Only
171//!
172//! Test timeout handling without errors (no type parameters needed!):
173//!
174//! ```rust
175//! use tower_resilience_chaos::ChaosLayer;
176//! use std::time::Duration;
177//!
178//! # async fn example() {
179//! // No type parameters required for latency-only chaos!
180//! let chaos = ChaosLayer::builder()
181//! .latency_rate(0.5) // 50% of requests delayed
182//! .min_latency(Duration::from_millis(100))
183//! .max_latency(Duration::from_millis(500))
184//! .build();
185//!
186//! // Use with TimeLimiter to test timeout behavior
187//! # }
188//! ```
189
190/// Configuration types for chaos injection.
191pub mod config;
192/// Event types emitted by chaos injection.
193pub mod events;
194/// Tower `Layer` implementation for chaos injection.
195pub mod layer;
196/// Tower `Service` implementation for chaos injection.
197pub mod service;
198
199pub use config::{
200 ChaosConfig, ChaosConfigBuilder, ChaosConfigBuilderWithRate, CustomErrorFn, ErrorInjector,
201 NoErrorInjection,
202};
203pub use events::ChaosEvent;
204pub use layer::ChaosLayer;
205pub use service::Chaos;
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use std::sync::atomic::{AtomicUsize, Ordering};
211 use std::sync::Arc;
212 use std::time::Duration;
213 use tower::{Layer, Service, ServiceExt};
214
215 #[tokio::test]
216 async fn test_no_chaos_passes_through() {
217 // Latency-only chaos - no type parameters needed!
218 let chaos = ChaosLayer::builder().build();
219
220 let mut service = chaos.layer(tower::service_fn(|req: String| async move {
221 Ok::<String, ()>(format!("echo: {}", req))
222 }));
223
224 let response = service
225 .ready()
226 .await
227 .unwrap()
228 .call("test".to_string())
229 .await
230 .unwrap();
231
232 assert_eq!(response, "echo: test");
233 }
234
235 #[tokio::test]
236 async fn test_error_injection_with_seed() {
237 // Error injection - types inferred from closure
238 let chaos = ChaosLayer::builder()
239 .error_rate(1.0) // Always fail
240 .error_fn(|_req: &String| "chaos error")
241 .seed(42)
242 .build();
243
244 let mut service = chaos.layer(tower::service_fn(|req: String| async move {
245 Ok::<String, &'static str>(req)
246 }));
247
248 let result = service
249 .ready()
250 .await
251 .unwrap()
252 .call("test".to_string())
253 .await;
254
255 assert!(result.is_err());
256 assert_eq!(result.unwrap_err(), "chaos error");
257 }
258
259 #[tokio::test]
260 async fn test_latency_injection() {
261 // Latency-only chaos - no type parameters needed!
262 let chaos = ChaosLayer::builder()
263 .latency_rate(1.0) // Always add latency
264 .min_latency(Duration::from_millis(50))
265 .max_latency(Duration::from_millis(50))
266 .seed(42)
267 .build();
268
269 let mut service = chaos.layer(tower::service_fn(|req: String| async move {
270 Ok::<String, ()>(req)
271 }));
272
273 let start = std::time::Instant::now();
274 let _response = service
275 .ready()
276 .await
277 .unwrap()
278 .call("test".to_string())
279 .await
280 .unwrap();
281 let elapsed = start.elapsed();
282
283 // Should have at least 50ms delay (with some tolerance for Windows)
284 assert!(
285 elapsed.as_millis() >= 40,
286 "Expected at least 40ms, got {}ms",
287 elapsed.as_millis()
288 );
289 }
290
291 #[tokio::test]
292 async fn test_event_listeners() {
293 let error_count = Arc::new(AtomicUsize::new(0));
294 let latency_count = Arc::new(AtomicUsize::new(0));
295 let pass_count = Arc::new(AtomicUsize::new(0));
296
297 let e = error_count.clone();
298 let l = latency_count.clone();
299 let p = pass_count.clone();
300
301 // Latency-only chaos with event listeners - no type parameters needed!
302 let chaos = ChaosLayer::builder()
303 .latency_rate(0.0)
304 .on_error_injected(move || {
305 e.fetch_add(1, Ordering::SeqCst);
306 })
307 .on_latency_injected(move |_delay| {
308 l.fetch_add(1, Ordering::SeqCst);
309 })
310 .on_passed_through(move || {
311 p.fetch_add(1, Ordering::SeqCst);
312 })
313 .build();
314
315 let mut service = chaos.layer(tower::service_fn(|req: String| async move {
316 Ok::<String, &'static str>(req)
317 }));
318
319 // Make a request that should pass through
320 let _response = service
321 .ready()
322 .await
323 .unwrap()
324 .call("test".to_string())
325 .await
326 .unwrap();
327
328 assert_eq!(error_count.load(Ordering::SeqCst), 0);
329 assert_eq!(latency_count.load(Ordering::SeqCst), 0);
330 assert_eq!(pass_count.load(Ordering::SeqCst), 1);
331 }
332
333 #[tokio::test]
334 async fn test_deterministic_behavior() {
335 // Create two services with the same seed
336 // Types inferred from closure
337 let make_service = || {
338 let chaos = ChaosLayer::builder()
339 .error_rate(0.5)
340 .error_fn(|_req: &String| "error")
341 .seed(123)
342 .build();
343
344 chaos.layer(tower::service_fn(|req: String| async move {
345 Ok::<String, &'static str>(req)
346 }))
347 };
348
349 let mut service1 = make_service();
350 let mut service2 = make_service();
351
352 // Make the same sequence of requests
353 for i in 0..10 {
354 let req = format!("req{}", i);
355 let result1 = service1.ready().await.unwrap().call(req.clone()).await;
356 let result2 = service2.ready().await.unwrap().call(req).await;
357
358 // Results should be identical
359 assert_eq!(result1.is_ok(), result2.is_ok());
360 }
361 }
362}