Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/lerouxrgd/recloser
A concurrent circuit breaker implemented with ring buffers
https://github.com/lerouxrgd/recloser
async circuit-breaker concurrent rust
Last synced: 5 days ago
JSON representation
A concurrent circuit breaker implemented with ring buffers
- Host: GitHub
- URL: https://github.com/lerouxrgd/recloser
- Owner: lerouxrgd
- License: mit
- Created: 2019-01-15T18:33:12.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2024-02-22T14:16:48.000Z (11 months ago)
- Last Synced: 2024-12-22T01:09:16.607Z (12 days ago)
- Topics: async, circuit-breaker, concurrent, rust
- Language: Rust
- Homepage:
- Size: 51.8 KB
- Stars: 118
- Watchers: 3
- Forks: 6
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# recloser [![latest]][crates.io] [![doc]][docs.rs]
[latest]: https://img.shields.io/crates/v/recloser.svg
[crates.io]: https://crates.io/crates/recloser
[doc]: https://docs.rs/recloser/badge.svg
[docs.rs]: https://docs.rs/recloserA concurrent [circuit breaker][cb] implemented with ring buffers.
The `Recloser` struct provides a `call(...)` method to wrap function calls that may
fail, it will eagerly reject them when some `failure_rate` is reached, and it will allow
them again after some time. A future aware version of `call(...)` is also available
through an `AsyncRecloser` wrapper.The API is largely based on [failsafe][] and the ring buffer implementation on
[resilient4j][].[cb]: https://martinfowler.com/bliki/CircuitBreaker.html
[failsafe]: https://github.com/dmexe/failsafe-rs
[resilient4j]: https://resilience4j.readme.io/docs/circuitbreaker## Usage
The `Recloser` can be in three states:
- `State::Closed(RingBuffer(len))`: The initial `Recloser`'s state. At least `len`
calls will be performed before calculating a `failure_rate` based on which
transitions to `State::Open(_)` state may happen.
- `State::Open(duration)`: All calls will return `Err(Error::Rejected)` until
`duration` has elapsed, then transition to `State::HalfOpen(_)` state will happen.
- `State::HalfOpen(RingBuffer(len))`: At least `len` calls will be performed before
calculating a `failure_rate` based on which transitions to either `State::Closed(_)`
or `State::Open(_)` states will happen.The state transition settings can be customized as follows:
```rust
use std::time::Duration;
use recloser::Recloser;// Equivalent to Recloser::default()
let recloser = Recloser::custom()
.error_rate(0.5)
.closed_len(100)
.half_open_len(10)
.open_wait(Duration::from_secs(30))
.build();
```Wrapping dangerous function calls in order to control failure propagation:
```rust
use recloser::{Recloser, Error};// Performs 1 call before calculating failure_rate
let recloser = Recloser::custom().closed_len(1).build();let f1 = || Err::<(), usize>(1);
// First call, just recorded as an error
let res = recloser.call(f1);
assert!(matches!(res, Err(Error::Inner(1))));// Now also computes failure_rate, that is 100% here
// Will transition to State::Open afterward
let res = recloser.call(f1);
assert!(matches!(res, Err(Error::Inner(1))));let f2 = || Err::<(), i64>(-1);
// All calls are rejected (while in State::Open)
let res = recloser.call(f2);
assert!(matches!(res, Err(Error::Rejected)));
```It is also possible to discard some errors on a per call basis.
This behavior is controlled by the `ErrorPredicate`trait, which is already
implemented for all `Fn(&E) -> bool`.```rust
use recloser::{Recloser, Error};let recloser = Recloser::default();
let f = || Err::<(), usize>(1);
// Custom predicate that doesn't consider usize values as errors
let p = |_: &usize| false;// Will not record resulting Err(1) as an error
let res = recloser.call_with(p, f);
assert!(matches!(res, Err(Error::Inner(1))));
```Wrapping functions that return `Future`s requires to use an `AsyncRecloser` that just
wraps a regular `Recloser`.```rust
use std::future;
use recloser::{Recloser, Error, AsyncRecloser};let recloser = AsyncRecloser::from(Recloser::default());
let future = future::ready::>(Err(1));
let future = recloser.call(future);
```## Performances
Benchmarks for `Recloser` and `failsafe::CircuitBreaker`
- Single threaded workload: same performances
- Multi threaded workload: `Recloser` has **10x** better performances```sh
recloser_simple time: [355.17 us 358.67 us 362.52 us]
failsafe_simple time: [403.47 us 406.90 us 410.29 us]
recloser_concurrent time: [668.44 us 674.26 us 680.48 us]
failsafe_concurrent time: [11.523 ms 11.613 ms 11.694 ms]
```These benchmarks were run on a `Intel Core i7-6700HQ @ 8x 3.5GHz` CPU.