Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

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

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/recloser

A 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.