├── .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 |
4 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
--------------------------------------------------------------------------------