├── .gitignore ├── .idea ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── misc.xml ├── .gitignore ├── modules.xml ├── php.xml └── git_toolbox_prj.xml ├── Cargo.toml ├── src ├── run_state.rs ├── lib.rs ├── failsafe_error.rs ├── policies │ ├── fallback.rs │ ├── rate_limiter.rs │ ├── timeout.rs │ ├── retry.rs │ ├── mod.rs │ └── circuit_breaker.rs ├── failsafe.rs ├── person.rs └── tests.rs ├── failsafe_rs.iml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "failsafe_rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | recloser = "1.1.0" 10 | thiserror = "1.0.38" 11 | rand = "0.8.5" 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/run_state.rs: -------------------------------------------------------------------------------- 1 | pub enum RunState { 2 | Success, 3 | TimeoutError, 4 | CircuitBreakerError, 5 | } 6 | 7 | #[derive(Debug, Clone)] 8 | pub enum PolicyActionState { 9 | Success, 10 | Retry, 11 | UsingFallback, 12 | TimeoutError, 13 | CircuitBreakerError, 14 | } 15 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | -------------------------------------------------------------------------------- /failsafe_rs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::failsafe_error::FailsafeError; 2 | use crate::policies::fallback::FallbackAble; 3 | use std::any::Any; 4 | use std::error::Error; 5 | use std::time::Duration; 6 | 7 | pub mod failsafe; 8 | pub mod failsafe_error; 9 | pub mod policies; 10 | pub mod run_state; 11 | 12 | // all objects that are being protected should implement Executable trait 13 | pub trait Runnable { 14 | fn run(&mut self) -> Result<(), Box>; 15 | fn update(&mut self, other: &Box); 16 | } 17 | 18 | #[cfg(test)] 19 | pub mod person; 20 | #[cfg(test)] 21 | mod tests; 22 | -------------------------------------------------------------------------------- /src/failsafe_error.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use thiserror::Error; 3 | 4 | #[derive(Error, Debug)] 5 | pub enum FailsafeError { 6 | #[error("Just a dummy error")] 7 | DummyError, 8 | #[error("Timeout error")] 9 | TimeoutError, 10 | #[error("Retry error")] 11 | RetryError, 12 | #[error("Runnable Error")] 13 | RunnableError(Box), 14 | #[error("Used Fallback")] 15 | UsedFallback, 16 | #[error("Unknown Error")] 17 | UnknownError, 18 | #[error("Circuit Breaker Open")] 19 | CircuitBreakerOpen, 20 | } 21 | 22 | impl FailsafeError { 23 | pub fn as_any(&self) -> &dyn Any { 24 | self 25 | } 26 | 27 | pub fn from_any(other: &Box) -> &Self { 28 | other.downcast_ref::().unwrap() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/policies/fallback.rs: -------------------------------------------------------------------------------- 1 | use crate::failsafe_error::FailsafeError; 2 | use crate::policies::{Policy, PolicyData}; 3 | use crate::run_state::PolicyActionState; 4 | use crate::Runnable; 5 | use std::any::Any; 6 | use std::fmt::Display; 7 | 8 | #[macro_export] 9 | macro_rules! on_fallback { 10 | ($f: expr) => { 11 | Box::new(move || -> Box { Box::new($f) }) 12 | }; 13 | } 14 | 15 | pub struct FallbackPolicy { 16 | fallback: Box Box>, 17 | policy_data: PolicyData, 18 | } 19 | 20 | impl FallbackPolicy { 21 | pub(crate) fn new(fallback: Box Box>) -> Self { 22 | FallbackPolicy { 23 | fallback, 24 | policy_data: PolicyData::default(), 25 | } 26 | } 27 | } 28 | 29 | impl Policy for FallbackPolicy { 30 | fn policy_data(&self) -> &PolicyData { 31 | &self.policy_data 32 | } 33 | 34 | fn policy_data_mut(&mut self) -> &mut PolicyData { 35 | &mut self.policy_data 36 | } 37 | 38 | fn name(&self) -> String { 39 | "FallbackPolicy".to_string() 40 | } 41 | 42 | fn policy_action( 43 | &mut self, 44 | runnable: &mut Box<&mut dyn Runnable>, 45 | ) -> Result { 46 | runnable.update(&(self.fallback)()); 47 | Ok(PolicyActionState::UsingFallback) 48 | } 49 | } 50 | 51 | pub trait FallbackAble { 52 | fn as_any(&self) -> &dyn Any; 53 | } 54 | -------------------------------------------------------------------------------- /src/policies/rate_limiter.rs: -------------------------------------------------------------------------------- 1 | use crate::failsafe_error::FailsafeError; 2 | use crate::policies::{Policy, PolicyData}; 3 | use crate::run_state::PolicyActionState; 4 | use crate::Runnable; 5 | use std::time::Duration; 6 | 7 | pub enum LimiterType { 8 | Smooth, 9 | Burst, 10 | } 11 | 12 | pub struct RateLimiter { 13 | policy_data: PolicyData, 14 | limiter_type: LimiterType, 15 | max_execution: i32, 16 | duration: Duration, 17 | can_run: bool, 18 | } 19 | 20 | impl RateLimiter { 21 | pub fn new(limiter_type: LimiterType, max_execution: i32, duration: Duration) -> Self { 22 | RateLimiter { 23 | policy_data: Default::default(), 24 | limiter_type, 25 | max_execution, 26 | duration, 27 | can_run: false, 28 | } 29 | } 30 | 31 | pub fn limiter_type(&self) -> &LimiterType { 32 | &self.limiter_type 33 | } 34 | pub fn max_execution(&self) -> i32 { 35 | self.max_execution 36 | } 37 | pub fn duration(&self) -> Duration { 38 | self.duration 39 | } 40 | } 41 | 42 | impl Policy for RateLimiter { 43 | fn policy_data(&self) -> &PolicyData { 44 | &self.policy_data 45 | } 46 | 47 | fn policy_data_mut(&mut self) -> &mut PolicyData { 48 | &mut self.policy_data 49 | } 50 | 51 | fn name(&self) -> String { 52 | "RateLimiter".to_string() 53 | } 54 | 55 | fn policy_action( 56 | &mut self, 57 | runnable: &mut Box<&mut dyn Runnable>, 58 | ) -> Result { 59 | todo!() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/policies/timeout.rs: -------------------------------------------------------------------------------- 1 | use crate::failsafe_error::FailsafeError; 2 | use crate::policies::{Policy, PolicyData}; 3 | use crate::run_state::PolicyActionState; 4 | use crate::Runnable; 5 | use std::any::Any; 6 | use std::borrow::Borrow; 7 | use std::time::{Duration, Instant}; 8 | 9 | pub struct TimeoutPolicy { 10 | timeout: Duration, 11 | policy_data: PolicyData, 12 | time_taken: Option, 13 | } 14 | 15 | impl TimeoutPolicy { 16 | pub(crate) fn new(timeout: Duration) -> Self { 17 | TimeoutPolicy { 18 | timeout, 19 | policy_data: Default::default(), 20 | time_taken: None, 21 | } 22 | } 23 | } 24 | 25 | impl Policy for TimeoutPolicy { 26 | fn policy_data(&self) -> &PolicyData { 27 | &self.policy_data 28 | } 29 | 30 | fn policy_data_mut(&mut self) -> &mut PolicyData { 31 | &mut self.policy_data 32 | } 33 | 34 | fn name(&self) -> String { 35 | "TimeoutPolicy".to_string() 36 | } 37 | 38 | fn run_guarded(&mut self, runnable: &mut Box<&mut dyn Runnable>) -> Result<(), FailsafeError> { 39 | let start = Instant::now(); 40 | let r = runnable.run(); 41 | self.time_taken = Some(start.elapsed()); 42 | if self.time_taken > Some(self.timeout) { 43 | self.policy_data.state = PolicyActionState::TimeoutError; 44 | return Err(FailsafeError::TimeoutError); 45 | } 46 | match r { 47 | Ok(_) => {} 48 | Err(e) => return Err(FailsafeError::RunnableError(e)), 49 | } 50 | Ok(()) 51 | } 52 | 53 | fn policy_action( 54 | &mut self, 55 | _: &mut Box<&mut dyn Runnable>, 56 | ) -> Result { 57 | match self.policy_data().state { 58 | PolicyActionState::TimeoutError => Err(FailsafeError::TimeoutError), 59 | _ => Ok(PolicyActionState::Success), 60 | } 61 | } 62 | 63 | fn reset(&mut self) { 64 | self.time_taken = None; 65 | self.inner_mut() 66 | .as_mut() 67 | .and_then(|inner| Some(inner.reset())); 68 | } 69 | } 70 | 71 | pub trait Interruptable { 72 | fn as_any(&self) -> &dyn Any; 73 | } 74 | -------------------------------------------------------------------------------- /src/policies/retry.rs: -------------------------------------------------------------------------------- 1 | use crate::failsafe_error::FailsafeError; 2 | use crate::policies::{Policy, PolicyData}; 3 | use crate::run_state::PolicyActionState; 4 | use crate::Runnable; 5 | use std::any::Any; 6 | use std::thread::sleep; 7 | use std::time::Duration; 8 | 9 | /// Retry policy, that retries given amount time with a delay before failing 10 | /// 11 | /// This policy will retry execution pipeline with given delay between attempts, if execution fails 12 | /// after retries have been exceeded, it will return `FailsafeError::Runnable` 13 | /// 14 | /// ## Features 15 | /// 16 | /// - [x] Retries 17 | /// - [x] Delay between retries 18 | /// - [ ] Back off [Link](https://failsafe.dev/javadoc/core/dev/failsafe/RetryPolicyBuilder.html#withBackoff-long-long-java.time.temporal.ChronoUnit-) 19 | /// - [ ] Random delay 20 | /// - [ ] Jitter [Check](https://failsafe.dev/javadoc/core/dev/failsafe/RetryPolicyBuilder.html#withJitter-double-) 21 | /// - [ ] No limit 22 | /// 23 | pub struct RetryPolicy { 24 | policy_data: PolicyData, 25 | retries: i32, 26 | delay: Duration, 27 | tries: i32, 28 | } 29 | 30 | impl RetryPolicy { 31 | pub(crate) fn new(retries: i32, delay: Duration) -> Self { 32 | let policy = RetryPolicy { 33 | policy_data: Default::default(), 34 | retries, 35 | delay, 36 | tries: 0, 37 | }; 38 | policy 39 | } 40 | } 41 | 42 | impl Policy for RetryPolicy { 43 | fn policy_data(&self) -> &PolicyData { 44 | &self.policy_data 45 | } 46 | 47 | fn policy_data_mut(&mut self) -> &mut PolicyData { 48 | &mut self.policy_data 49 | } 50 | 51 | fn name(&self) -> String { 52 | "RetryPolicy".to_string() 53 | } 54 | 55 | fn policy_action( 56 | &mut self, 57 | _: &mut Box<&mut dyn Runnable>, 58 | ) -> Result { 59 | self.tries += 1; 60 | return if self.tries >= self.retries { 61 | self.tries = 0; 62 | Err(FailsafeError::RetryError) 63 | } else { 64 | sleep(self.delay); 65 | Ok(PolicyActionState::Retry) 66 | }; 67 | } 68 | 69 | fn reset(&mut self) { 70 | self.tries = 0; 71 | self.inner_mut() 72 | .as_mut() 73 | .and_then(|mut inner| Some(inner.reset())); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/failsafe.rs: -------------------------------------------------------------------------------- 1 | use crate::failsafe_error::FailsafeError; 2 | use crate::policies::Policy; 3 | use crate::run_state::PolicyActionState; 4 | use crate::Runnable; 5 | use std::any::Any; 6 | use std::borrow::{Borrow, BorrowMut}; 7 | use std::error::Error; 8 | 9 | /// Failsafe is a simple library for handling failures. It tries to resemble Failsafe for Java closely. 10 | 11 | type FailsafeRunnableResult = Result>, FailsafeError>; 12 | 13 | #[macro_export] 14 | macro_rules! failsafe { 15 | ( 16 | $x:tt; 17 | $( [ $( $y:expr ),* ]);* 18 | ) => { 19 | $x::new($($( $y ),*),*) 20 | }; 21 | 22 | ([ 23 | $( 24 | $x:tt; $( [ $( $y:expr ),* ])* 25 | ),* 26 | ]) => { 27 | Failsafe::builder() 28 | $(.push(failsafe!($x; [$($( $y ),*),*])))* 29 | .build() 30 | } 31 | } 32 | 33 | pub struct Failsafe { 34 | policy: Box, 35 | state: PolicyActionState, 36 | } 37 | 38 | impl Failsafe { 39 | pub fn run<'a, T: Runnable>(&'a mut self, protected: &'a mut T) -> Result<(), FailsafeError> { 40 | let mut errors = vec![]; 41 | let k = self.policy.run(&mut Box::new(protected), &mut errors); 42 | println!("{:?}", errors); 43 | k 44 | } 45 | 46 | pub(crate) fn state(&self) -> PolicyActionState { 47 | PolicyActionState::Success 48 | } 49 | 50 | pub fn builder() -> FailsafeBuilder { 51 | FailsafeBuilder::new() 52 | } 53 | 54 | pub fn policy(&self) -> &Box { 55 | &self.policy 56 | } 57 | } 58 | 59 | pub struct FailsafeBuilder { 60 | policies: Vec>, 61 | } 62 | 63 | impl FailsafeBuilder { 64 | fn new() -> FailsafeBuilder { 65 | FailsafeBuilder { policies: vec![] } 66 | } 67 | } 68 | 69 | impl FailsafeBuilder { 70 | pub fn push(&mut self, policy: T) -> &mut Self { 71 | self.policies.push(Box::new(policy)); 72 | self 73 | } 74 | 75 | pub(crate) fn build(&mut self) -> Failsafe { 76 | if self.policies.is_empty() { 77 | panic!("No policy or runnable provided.") 78 | } 79 | let mut first = self.policies.pop().unwrap(); 80 | while !self.policies.is_empty() { 81 | let mut current = self.policies.pop().unwrap(); 82 | current.set_inner(first); 83 | first = current; 84 | } 85 | Failsafe { 86 | policy: first, 87 | state: PolicyActionState::Success, 88 | } 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | } 96 | -------------------------------------------------------------------------------- /src/policies/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::failsafe_error::FailsafeError; 2 | use crate::run_state::PolicyActionState; 3 | use crate::Runnable; 4 | use std::any::Any; 5 | use std::error::Error; 6 | 7 | pub mod circuit_breaker; 8 | pub mod fallback; 9 | pub mod rate_limiter; 10 | pub mod retry; 11 | pub mod timeout; 12 | 13 | pub struct PolicyData { 14 | state: PolicyActionState, 15 | runnable_error: Box, 16 | inner: Option>, 17 | } 18 | 19 | impl Default for PolicyData { 20 | fn default() -> Self { 21 | PolicyData { 22 | state: PolicyActionState::Success, 23 | runnable_error: Box::new(()), 24 | inner: None, 25 | } 26 | } 27 | } 28 | 29 | pub trait Policy { 30 | fn policy_data(&self) -> &PolicyData; 31 | fn policy_data_mut(&mut self) -> &mut PolicyData; 32 | 33 | fn inner(&self) -> &Option> { 34 | &self.policy_data().inner 35 | } 36 | 37 | fn inner_mut(&mut self) -> &mut Option> { 38 | &mut self.policy_data_mut().inner 39 | } 40 | 41 | fn set_inner(&mut self, inner: Box) { 42 | self.policy_data_mut().inner = Some(inner); 43 | } 44 | 45 | fn state(&self) -> &PolicyActionState { 46 | &self.policy_data().state 47 | } 48 | 49 | fn set_state(&mut self, state: PolicyActionState) { 50 | self.policy_data_mut().state = state; 51 | } 52 | 53 | fn runnable_error(&self) -> &Box { 54 | &self.policy_data().runnable_error 55 | } 56 | 57 | fn set_runnable_error(&mut self, err: Box) { 58 | self.policy_data_mut().runnable_error = err; 59 | } 60 | 61 | fn name(&self) -> String; 62 | 63 | fn run( 64 | &mut self, 65 | mut runnable: &mut Box<&mut dyn Runnable>, 66 | policy_errors: &mut Vec, 67 | ) -> Result<(), FailsafeError> { 68 | loop { 69 | self.before_run(); 70 | let e = if self.inner_mut().is_some() { 71 | let result = self 72 | .inner_mut() 73 | .as_mut() 74 | .and_then(|inner| Some(inner.run(runnable, policy_errors))) 75 | .unwrap(); 76 | match result { 77 | Ok(_) => { 78 | self.reset(); 79 | return Ok(()); 80 | } 81 | Err(e) => e, 82 | } 83 | } else { 84 | match self.run_guarded(runnable) { 85 | Ok(_) => return Ok(()), 86 | Err(e) => e, 87 | } 88 | }; 89 | policy_errors.push(e); 90 | let result = self.policy_action(&mut runnable); 91 | if result.is_err() { 92 | return Err(result.err().unwrap()); 93 | } 94 | return match result { 95 | Ok(PolicyActionState::Success) => { 96 | self.reset(); 97 | Ok(()) 98 | } 99 | Ok(PolicyActionState::Retry) => continue, 100 | Ok(PolicyActionState::UsingFallback) => Err(FailsafeError::UsedFallback), 101 | _ => Ok(()), 102 | }; 103 | } 104 | } 105 | 106 | // does nothing for now. 107 | fn before_run(&self) {} 108 | 109 | fn run_guarded(&mut self, runnable: &mut Box<&mut dyn Runnable>) -> Result<(), FailsafeError> { 110 | let result = runnable.run(); 111 | match result { 112 | Ok(_) => { 113 | self.reset(); 114 | return Ok(()); 115 | } 116 | Err(e) => Err(FailsafeError::RunnableError(e)), 117 | } 118 | } 119 | 120 | fn policy_action( 121 | &mut self, 122 | runnable: &mut Box<&mut dyn Runnable>, 123 | ) -> Result; 124 | 125 | fn reset(&mut self) { 126 | self.inner_mut() 127 | .as_mut() 128 | .and_then(|inner| Some(inner.reset())); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/person.rs: -------------------------------------------------------------------------------- 1 | use crate::policies::fallback::FallbackAble; 2 | use crate::policies::timeout::Interruptable; 3 | use crate::Runnable; 4 | use rand::distributions::{Alphanumeric, DistString}; 5 | use rand::random; 6 | /// a structure to test failsafe 7 | use std::any::Any; 8 | use std::thread::sleep; 9 | use std::time::Duration; 10 | use thiserror::Error; 11 | 12 | #[derive(Error, Debug, Clone, PartialEq)] 13 | pub enum PersonError { 14 | #[error("Failed to find name for")] 15 | NameFindingError, 16 | } 17 | 18 | impl PersonError { 19 | pub fn as_any(&self) -> &dyn Any { 20 | self 21 | } 22 | 23 | pub fn from_any(other: &Box) -> &Self { 24 | other.downcast_ref::().unwrap() 25 | } 26 | } 27 | 28 | #[derive(Clone, Debug, PartialEq)] 29 | pub struct Person { 30 | name: Option, 31 | // the followings are for helping test 32 | _always_fail: bool, 33 | _fail_pattern: Option>, 34 | _bk_fail_pattern: Option>, 35 | _wait_for: Option, 36 | } 37 | 38 | impl Person { 39 | pub(crate) fn set_name(&mut self, name: &str) { 40 | self.name = Some(name.to_string()); 41 | } 42 | } 43 | 44 | impl Person { 45 | pub fn new() -> Self { 46 | Person { 47 | name: None, 48 | _always_fail: false, 49 | _fail_pattern: None, 50 | _bk_fail_pattern: None, 51 | _wait_for: None, 52 | } 53 | } 54 | 55 | pub fn with_name(name: &str) -> Self { 56 | Person { 57 | name: Some(name.to_string()), 58 | _always_fail: false, 59 | _fail_pattern: None, 60 | _bk_fail_pattern: None, 61 | _wait_for: None, 62 | } 63 | } 64 | 65 | pub fn name(&self) -> String { 66 | self.name.clone().unwrap() 67 | } 68 | 69 | pub fn set_always_fail(&mut self, always_fail: bool) { 70 | self._always_fail = always_fail; 71 | } 72 | 73 | pub fn set_fail_pattern(&mut self, fail_pattern: Vec) { 74 | if fail_pattern.len() == 0 { 75 | self._fail_pattern = None; 76 | self._bk_fail_pattern = None; 77 | return; 78 | } 79 | let mut k = fail_pattern.clone(); 80 | k.reverse(); 81 | self._bk_fail_pattern = Some(k.clone()); 82 | self._fail_pattern = Some(k); 83 | } 84 | 85 | pub fn set_wait_for(&mut self, d: Duration) { 86 | self._wait_for = Some(d); 87 | } 88 | } 89 | 90 | impl Runnable for Person { 91 | fn run(&mut self) -> Result<(), Box> { 92 | println!("I am a person, getting my name!"); 93 | let name = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); 94 | // Followings are for testing only 95 | { 96 | let error: bool = if self._fail_pattern != None { 97 | if self._bk_fail_pattern.as_ref().unwrap().is_empty() { 98 | self._bk_fail_pattern = self._fail_pattern.clone(); 99 | } 100 | self._bk_fail_pattern 101 | .as_mut() 102 | .and_then(|v| v.pop()) 103 | .unwrap() 104 | } else if self._always_fail { 105 | true 106 | } else { 107 | random() 108 | }; 109 | if self._wait_for.is_some() { 110 | self._wait_for.as_ref().and_then(|v| { 111 | sleep(*v); 112 | Some(()) 113 | }); 114 | } 115 | println!("{}", error); 116 | if error { 117 | println!("Couldn't get a name!"); 118 | return Err(Box::new(PersonError::as_any( 119 | &PersonError::NameFindingError, 120 | ))); 121 | } 122 | } 123 | println!("Got a name! {}", name); 124 | self.name = Some(name); 125 | Ok(()) 126 | } 127 | 128 | fn update(&mut self, other: &Box) { 129 | let n: &Person = other.as_any().downcast_ref().unwrap(); 130 | self.name = Some(n.name().clone()); 131 | } 132 | } 133 | 134 | impl FallbackAble for Person { 135 | fn as_any(&self) -> &dyn Any { 136 | self 137 | } 138 | } 139 | 140 | impl Interruptable for Person { 141 | fn as_any(&self) -> &dyn Any { 142 | self 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/policies/circuit_breaker.rs: -------------------------------------------------------------------------------- 1 | use crate::failsafe_error::FailsafeError; 2 | use crate::policies::{Policy, PolicyData}; 3 | use crate::run_state::PolicyActionState; 4 | use crate::Runnable; 5 | use std::any::Any; 6 | use std::time::{Duration, Instant}; 7 | 8 | #[derive(PartialEq, Debug)] 9 | pub enum CircuitBreakerState { 10 | Closed, 11 | Open, 12 | HalfOpen, 13 | } 14 | 15 | pub struct CircuitBreakerPolicy { 16 | policy_data: PolicyData, 17 | circuit_breaker_state: CircuitBreakerState, 18 | failure_threshold: i32, 19 | success_threshold: i32, 20 | delay: Duration, 21 | last_attempt: Option, 22 | failure_count: i32, 23 | success_count: i32, 24 | } 25 | 26 | impl CircuitBreakerPolicy { 27 | pub fn new(failure_threshold: i32, delay: Duration, success_threshold: i32) -> Self { 28 | CircuitBreakerPolicy { 29 | policy_data: PolicyData { 30 | state: PolicyActionState::Success, 31 | runnable_error: Box::new(()), 32 | inner: None, 33 | }, 34 | circuit_breaker_state: CircuitBreakerState::Closed, 35 | failure_threshold, 36 | success_threshold, 37 | delay, 38 | last_attempt: None, 39 | failure_count: 0, 40 | success_count: 0, 41 | } 42 | } 43 | 44 | pub fn circuit_breaker_state(&self) -> &CircuitBreakerState { 45 | &self.circuit_breaker_state 46 | } 47 | pub fn failure_threshold(&self) -> i32 { 48 | self.failure_threshold 49 | } 50 | pub fn success_threshold(&self) -> i32 { 51 | self.success_threshold 52 | } 53 | pub fn delay(&self) -> Duration { 54 | self.delay 55 | } 56 | pub fn last_attempt(&self) -> Option { 57 | self.last_attempt 58 | } 59 | pub fn failure_count(&self) -> i32 { 60 | self.failure_count 61 | } 62 | pub fn success_count(&self) -> i32 { 63 | self.success_count 64 | } 65 | } 66 | 67 | impl Policy for CircuitBreakerPolicy { 68 | fn policy_data(&self) -> &PolicyData { 69 | &self.policy_data 70 | } 71 | 72 | fn policy_data_mut(&mut self) -> &mut PolicyData { 73 | &mut self.policy_data 74 | } 75 | 76 | fn name(&self) -> String { 77 | "CircuitBreakerPolicy".to_string() 78 | } 79 | 80 | fn run_guarded(&mut self, runnable: &mut Box<&mut dyn Runnable>) -> Result<(), FailsafeError> { 81 | if self.circuit_breaker_state == CircuitBreakerState::Open { 82 | let now = Instant::now(); 83 | if let Some(last_attempt) = self.last_attempt { 84 | if now - last_attempt > self.delay { 85 | self.circuit_breaker_state = CircuitBreakerState::HalfOpen; 86 | } else { 87 | return Err(FailsafeError::CircuitBreakerOpen); 88 | } 89 | } else { 90 | return Err(FailsafeError::CircuitBreakerOpen); 91 | } 92 | } 93 | self.last_attempt = Some(Instant::now()); 94 | match runnable.run() { 95 | Ok(_) => match self.circuit_breaker_state { 96 | CircuitBreakerState::Closed => { 97 | self.reset(); 98 | Ok(()) 99 | } 100 | CircuitBreakerState::HalfOpen => { 101 | self.success_count += 1; 102 | if self.success_count >= self.success_threshold { 103 | self.reset(); 104 | } 105 | Ok(()) 106 | } 107 | CircuitBreakerState::Open => Err(FailsafeError::CircuitBreakerOpen), 108 | }, 109 | Err(e) => { 110 | self.policy_data_mut().state = PolicyActionState::CircuitBreakerError; 111 | Err(FailsafeError::RunnableError(e)) 112 | } 113 | } 114 | } 115 | 116 | fn policy_action( 117 | &mut self, 118 | _: &mut Box<&mut dyn Runnable>, 119 | ) -> Result { 120 | match self.policy_data().state { 121 | PolicyActionState::CircuitBreakerError => { 122 | self.failure_count += 1; 123 | if self.failure_count >= self.failure_threshold { 124 | self.circuit_breaker_state = CircuitBreakerState::Open 125 | } 126 | Err(FailsafeError::CircuitBreakerOpen) 127 | } 128 | _ => Ok(PolicyActionState::Success), 129 | } 130 | } 131 | 132 | fn reset(&mut self) { 133 | self.last_attempt = None; 134 | self.failure_count = 0; 135 | self.success_count = 0; 136 | self.circuit_breaker_state = CircuitBreakerState::Closed; 137 | self.inner_mut() 138 | .as_mut() 139 | .and_then(|inner| Some(inner.reset())); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Failsafe 2 | 3 | Failsafe is a lightweight rust library for handling failures. 4 | 5 | ## How to use 6 | 7 | A failsafe client must implement the `Runnable` trait 8 | ```rust 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub struct Person { 11 | name: Option, 12 | pub url: String, 13 | } 14 | 15 | impl Runnable for Person { 16 | fn run(&mut self) -> Result<(), Box> { 17 | println!("I am a person, getting my name!"); 18 | let name_response: Result = remote_request_that_might_fail(self.url); 19 | match name_response { 20 | Ok(name) => { 21 | println!("Got a name! {}", name); 22 | self.name = Some(name); 23 | } 24 | Err(_) => return Err( 25 | Box::new( 26 | PersonError::as_any(&PersonError::NameFindingError) 27 | ) 28 | ) 29 | } 30 | Ok(()) 31 | } 32 | 33 | fn update(&mut self, other: &Box) { 34 | let n: &Person = other.as_any().downcast_ref().unwrap(); 35 | self.url = n.url; 36 | } 37 | } 38 | ``` 39 | 40 | Client that will use `FallbackPolicy`, must implement `FallbackAble` Trait 41 | ```rust 42 | impl FallbackAble for Person { 43 | fn as_any(&self) -> &dyn Any { 44 | self 45 | } 46 | } 47 | 48 | ``` 49 | 50 | There are two ways we can create `Failsafe` Object composing of multiple policies 51 | 52 | ## Using Macro 53 | ```rust 54 | fn using_macro() -> FailSafe { 55 | let mut k = 0; 56 | let url_list = vec!["", "google.com", "bing.com", "duckduckgo.com"]; 57 | failsafe!([ 58 | RetryPolicy; [1, Duration::from_millis(50)], 59 | FallbackPolicy; [on_fallback!({ 60 | k += 1; 61 | if k >= url_list.len() { 62 | k = 0 63 | } 64 | Person::with_url(url_list[k]) 65 | })], 66 | RetryPolicy; [3, Duration::from_millis(50)] 67 | ]) 68 | } 69 | ``` 70 | 71 | ## Using Builder 72 | ```rust 73 | fn using_builder() -> FailSafe { 74 | let mut k = 0; 75 | let url_list = vec!["", "google.com", "bing.com", "duckduckgo.com"]; 76 | Failsafe::builder() 77 | .push(RetryPolicy::new(1, Duration::from_millis(50))) 78 | .push(FallbackPolicy::new(on_fallback!({ 79 | k += 1; 80 | if k >= url_list.len() { 81 | k = 0 82 | } 83 | Person::with_url(url_list[k]) 84 | }))) 85 | .push(RetryPolicy::new(3, Duration::from_millis(50))) 86 | .build() 87 | } 88 | ``` 89 | 90 | Once the `FailSafe` object is created, we can pass any `Runnable` client and run it. 91 | 92 | 93 | In this following example, using the above policy set, the process is as followed: 94 | 95 | 1. Failsafe runs the error-prone client, 96 | 2. The client fails, and returns an error 97 | 3. Retry policy waits for 50 ms, and tries again 98 | 4. In case the client success, retry policy resets and returns Ok 99 | 5. In case the client fails, retry policy tries 2 more times 100 | 6. If all the attempts are failed, retry policy hands the runnable to FallbackPolicy 101 | 7. FallbackPolicy, assigns a fallback url, and hands the runnable to next policy, which is another RetryPolicy 102 | 8. RetryPolicy starts the execution process from the beginning, with a new url 103 | 104 | ```rust 105 | fn main() { 106 | let mut safe = using_macro(); 107 | let mut person = Person::new(); 108 | let person_result: Result<(), FailsafeError> = safe.run(&mut person); 109 | 110 | let mut another = AnotherClient::new(); 111 | let another_result: Result<(), FailsafeError> = safe.run(&mut another); 112 | } 113 | ``` 114 | 115 | # Policies, Features, Roadmap 116 | 117 | ## Common features 118 | - [ ] Cooperative Cancellation 119 | - [ ] Cooperative interruption 120 | - [ ] Propagating Cancellations 121 | - [ ] Interruptions 122 | - [ ] Event Listeners 123 | 124 | ## Retry Policy 125 | Retry policy, that retries given amount time with a delay before failing 126 | 127 | This policy will retry execution pipeline with given delay between attempts, if execution fails 128 | after retries have been exceeded, it will return `FailsafeError::Runnable` 129 | 130 | ### Features 131 | 132 | - [x] Retries 133 | - [x] Delay between retries 134 | - [ ] Backoff [Ref](https://failsafe.dev/javadoc/core/dev/failsafe/RetryPolicyBuilder.html#withBackoff-long-long-java.time.temporal.ChronoUnit-) 135 | - [ ] Random delay 136 | - [ ] Jitter [Ref](https://failsafe.dev/javadoc/core/dev/failsafe/RetryPolicyBuilder.html#withJitter-double-) 137 | - [ ] No limit 138 | 139 | 140 | ## Circuit Breaker 141 | Circuit Breaker will temporarily disable executions after failure threshold exceeded the configured limit. 142 | 143 | Failsafe Java has two types of implementations 144 | 145 | - [x] *Count based*: Count based circuit breakers operate by tracking recent execution results up to a certain limit. 146 | - [ ] *Time based*: Time based circuit breakers operate by tracking any number of execution results that occur within a time period. 147 | 148 | Once the failure limit has been reached the breaker will open and next executions will fail with `CircuitBreakerOpen` error. After the configured time, it will be half-opened and some executions are allowed. If this trial executions are successful, the circuit is closed again, and normal operation resumes. Otherwise, it reopened. 149 | 150 | ### Features 151 | - [ ] Metrics 152 | - [ ] [Time based resolution](https://failsafe.dev/circuit-breaker/#time-based-resolution) 153 | 154 | ## Timeout 155 | Timeout if the execution is not completed within the given time. 156 | - [x] Timing out 157 | 158 | ## Rate limiters 159 | 160 | [Ref](https://failsafe.dev/rate-limiter/) 161 | 162 | - [ ] Not implemented 163 | - [ ] Smooth 164 | - [ ] Bursty 165 | 166 | It's no async, so I can implement it 167 | 168 | 169 | ## Bulkhead 170 | 171 | [Ref](https://failsafe.dev/bulkhead/) 172 | 173 | This one's async. 174 | 175 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::person::{Person, PersonError}; 3 | use crate::policies::circuit_breaker::{CircuitBreakerPolicy, CircuitBreakerState}; 4 | use crate::policies::rate_limiter::{LimiterType, RateLimiter}; 5 | use crate::{ 6 | failsafe::Failsafe, 7 | failsafe_error::FailsafeError, 8 | policies::Policy, 9 | policies::{fallback::FallbackPolicy, retry::RetryPolicy, timeout::TimeoutPolicy}, 10 | }; 11 | use std::thread::sleep; 12 | use std::time::{Duration, Instant}; 13 | 14 | fn check_expected_error(r: Result<(), FailsafeError>, expected: &str) -> bool { 15 | match r { 16 | Ok(_) => false, 17 | Err(e) => { 18 | let s = format!("{:?}", e); 19 | &s == expected 20 | } 21 | } 22 | } 23 | 24 | #[test] 25 | fn fallback_callback() { 26 | let mut k = 0; 27 | let mut safe = failsafe!([ 28 | RetryPolicy; [1, Duration::from_millis(50)], 29 | FallbackPolicy; [on_fallback!({ 30 | k += 1; 31 | let s = format!("Person {}", k); 32 | let mut p = Person::new(); 33 | p.set_name(&s); 34 | p 35 | })], 36 | RetryPolicy; [3, Duration::from_millis(50)] 37 | ]); 38 | let mut person = Person::new(); 39 | person.set_always_fail(true); 40 | let person_result = { safe.run(&mut person) }; 41 | assert_eq!("Person 1", person.name()); 42 | let person_result = { safe.run(&mut person) }; 43 | assert_eq!("Person 2", person.name()); 44 | let person_result = { safe.run(&mut person) }; 45 | assert_eq!("Person 3", person.name()); 46 | } 47 | 48 | #[test] 49 | fn test_fallback() { 50 | let mut safe = failsafe!([FallbackPolicy; [on_fallback!({ Person::with_name("No Name") })]]); 51 | let mut person = Person::new(); 52 | person.set_always_fail(true); 53 | let person_result = { safe.run(&mut person) }; 54 | assert!(check_expected_error(person_result, "UsedFallback")); 55 | } 56 | 57 | #[test] 58 | fn test_retry_policy_with_always_failing() { 59 | let mut safe = failsafe!([RetryPolicy; [3, Duration::from_millis(50)]]); 60 | let mut person = Person::new(); 61 | person.set_always_fail(true); 62 | let person_result = { safe.run(&mut person) }; 63 | assert!(check_expected_error(person_result, "RetryError")); 64 | } 65 | 66 | #[test] 67 | fn test_retry_policy_working_after_few_retries() { 68 | let mut safe = failsafe!([RetryPolicy; [3, Duration::from_millis(50)]]); 69 | let mut person = Person::new(); 70 | person.set_fail_pattern(vec![false, true, true, false]); 71 | let person_result = { safe.run(&mut person) }; 72 | assert!(person_result.is_ok()); 73 | } 74 | 75 | #[test] 76 | fn retry_policy_on_fail() { 77 | let mut safe = failsafe!([RetryPolicy; [3, Duration::from_millis(50)]]); 78 | let mut person = Person::new(); 79 | person.set_always_fail(true); 80 | let person_result = { safe.run(&mut person) }; 81 | assert_eq!( 82 | person_result.expect_err("What error!").to_string(), 83 | FailsafeError::RetryError.to_string() 84 | ); 85 | } 86 | 87 | #[test] 88 | fn test_if_retry_policy_multiple_run_correctly_reset() { 89 | let mut safe = failsafe!([RetryPolicy; [3, Duration::from_millis(50)]]); 90 | let mut person = Person::new(); 91 | person.set_fail_pattern(vec![false, true, true]); 92 | let person_result = { safe.run(&mut person) }; 93 | assert!(person_result.is_ok()); 94 | // should be same 95 | let mut person = Person::new(); 96 | person.set_fail_pattern(vec![false, true, true]); 97 | let person_result = { safe.run(&mut person) }; 98 | assert!(person_result.is_ok()); 99 | } 100 | 101 | #[test] 102 | fn test_using_different_value_from_fallback() { 103 | let mut safe = { 104 | let mut k = 0; 105 | let name_list = vec!["", "Picard", "Riker", "Data"]; 106 | failsafe!([ 107 | RetryPolicy; [1, Duration::from_millis(50)], 108 | FallbackPolicy; [on_fallback!({ 109 | k += 1; 110 | if k >= name_list.len() { 111 | k = 0 112 | } 113 | Person::with_name(name_list[k]) 114 | })], 115 | RetryPolicy; [3, Duration::from_millis(50)] 116 | ]) 117 | }; 118 | let mut person = Person::new(); 119 | person.set_always_fail(true); 120 | let _ = { safe.run(&mut person) }; 121 | assert_eq!("Picard", person.name()); 122 | let _ = { safe.run(&mut person) }; 123 | assert_eq!("Riker", person.name()); 124 | let _ = { safe.run(&mut person) }; 125 | assert_eq!("Data", person.name()); 126 | } 127 | 128 | #[test] 129 | fn failsafe_builder_marco() { 130 | let safe = failsafe!([ 131 | FallbackPolicy; [on_fallback!({ 132 | Person::with_name("No Name") 133 | })], 134 | RetryPolicy; [3, Duration::from_millis(50)] 135 | ]); 136 | assert_eq!(safe.policy().name(), "FallbackPolicy"); 137 | let mut p = safe.policy().as_ref().clone(); 138 | let k = p.inner().as_ref().unwrap().name(); 139 | assert_eq!(&k, "RetryPolicy"); 140 | } 141 | 142 | #[test] 143 | fn failsafe_builder() { 144 | let safe = Failsafe::builder() 145 | .push(FallbackPolicy::new(on_fallback!({ 146 | Person::with_name("No Name") 147 | }))) 148 | .push(RetryPolicy::new(3, Duration::from_millis(50))) 149 | .build(); 150 | assert_eq!(safe.policy().name(), "FallbackPolicy"); 151 | let mut p = safe.policy().as_ref().clone(); 152 | let k = p.inner().as_ref().unwrap().name(); 153 | assert_eq!(&k, "RetryPolicy"); 154 | } 155 | 156 | #[test] 157 | fn timeout_policy_test() { 158 | let mut safe = failsafe!([TimeoutPolicy; [Duration::from_millis(1000)]]); 159 | let mut person = Person::new(); 160 | let person_result = { safe.run(&mut person) }; 161 | assert!(person_result.is_ok()); 162 | 163 | person.set_wait_for(Duration::from_millis(2100)); 164 | let person_result = { safe.run(&mut person) }; 165 | assert!(person_result.is_err()); 166 | check_expected_error(person_result, "TimeoutError"); 167 | 168 | person.set_wait_for(Duration::from_millis(100)); 169 | person.set_always_fail(true); 170 | let person_result = { safe.run(&mut person) }; 171 | assert!(person_result.is_err()); 172 | if let Err(FailsafeError::RunnableError(_e)) = person_result { 173 | assert_eq!(&PersonError::NameFindingError, PersonError::from_any(&_e)); 174 | } 175 | } 176 | 177 | #[test] 178 | fn circuit_breaker_impl() { 179 | let mut person = Person::new(); 180 | person.set_fail_pattern(vec![ 181 | false, // check if normal run possible 182 | true, true, true, true, true, false, // now reach the error threshold 183 | false, false, // this should be circuit breaker open error 184 | false, false, false, // now let's check if circuit breaker closes on success 185 | false, // now it should be all open 186 | ]); 187 | 188 | let mut policy = CircuitBreakerPolicy::new(5, Duration::from_millis(20), 2); 189 | let mut policy_errors = vec![]; 190 | // check if normal run possible 191 | assert!(policy 192 | .run(&mut Box::new(&mut person), &mut policy_errors) 193 | .is_ok()); 194 | assert_eq!(policy_errors.len(), 0); 195 | policy_errors.clear(); 196 | println!("Error runs ..."); 197 | for _ in 0..=5 { 198 | let e = policy.run(&mut Box::new(&mut person), &mut policy_errors); 199 | if let Err(FailsafeError::RunnableError(_e)) = e { 200 | assert_eq!(&PersonError::NameFindingError, PersonError::from_any(&_e)); 201 | } 202 | } 203 | println!("> {:?}", policy_errors); 204 | policy_errors.clear(); 205 | assert_eq!(policy.circuit_breaker_state(), &CircuitBreakerState::Open); 206 | for _ in 0..=2 { 207 | println!("Running ..."); 208 | let r = policy.run(&mut Box::new(&mut person), &mut policy_errors); 209 | if let Err(FailsafeError::CircuitBreakerOpen) = r { 210 | continue; 211 | } 212 | assert!(false) 213 | } 214 | sleep(Duration::from_millis(22)); 215 | for _ in 0..1 { 216 | assert!(policy 217 | .run(&mut Box::new(&mut person), &mut policy_errors) 218 | .is_ok()); 219 | assert_eq!( 220 | policy.circuit_breaker_state(), 221 | &CircuitBreakerState::HalfOpen 222 | ); 223 | } 224 | assert!(policy 225 | .run(&mut Box::new(&mut person), &mut policy_errors) 226 | .is_ok()); 227 | assert_eq!(policy.circuit_breaker_state(), &CircuitBreakerState::Closed); 228 | person.set_fail_pattern(vec![]); 229 | person.set_always_fail(true); 230 | for _ in 0..=5 { 231 | let e = policy.run(&mut Box::new(&mut person), &mut policy_errors); 232 | if let Err(FailsafeError::RunnableError(_e)) = e { 233 | assert_eq!(&PersonError::NameFindingError, PersonError::from_any(&_e)); 234 | } 235 | } 236 | println!("> {:?}", policy_errors); 237 | policy_errors.clear(); 238 | if let Err(FailsafeError::CircuitBreakerOpen) = 239 | policy.run(&mut Box::new(&mut person), &mut policy_errors) 240 | { 241 | assert!(true); 242 | } else { 243 | assert!(false); 244 | } 245 | sleep(Duration::from_millis(22)); 246 | person.set_fail_pattern(vec![false, true]); 247 | assert!(policy 248 | .run(&mut Box::new(&mut person), &mut policy_errors) 249 | .is_ok()); 250 | assert!(policy 251 | .run(&mut Box::new(&mut person), &mut policy_errors) 252 | .is_err()); 253 | assert_eq!(policy.circuit_breaker_state(), &CircuitBreakerState::Open); 254 | println!("{:?}", policy_errors); 255 | } 256 | 257 | #[test] 258 | fn rate_limiter_impl() { 259 | let mut policy = RateLimiter::new(LimiterType::Smooth, 100, Duration::from_secs(1)); 260 | let mut p = Person::new(); 261 | let mut policy_errors = vec![]; 262 | let start = Instant::now(); 263 | for _ in 0..200 { 264 | match policy.run(&mut Box::new(&mut p), &mut policy_errors) { 265 | Ok(_) => {} 266 | Err(_) => {} 267 | } 268 | } 269 | } 270 | --------------------------------------------------------------------------------