{"id":19958260,"url":"https://github.com/lerouxrgd/recloser","last_synced_at":"2025-04-05T10:08:38.448Z","repository":{"id":49981501,"uuid":"165902965","full_name":"lerouxrgd/recloser","owner":"lerouxrgd","description":"A concurrent circuit breaker implemented with ring buffers","archived":false,"fork":false,"pushed_at":"2024-02-22T14:16:48.000Z","size":53,"stargazers_count":119,"open_issues_count":1,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-29T09:09:29.494Z","etag":null,"topics":["async","circuit-breaker","concurrent","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lerouxrgd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-15T18:33:12.000Z","updated_at":"2025-02-07T10:28:46.000Z","dependencies_parsed_at":"2022-09-10T18:41:04.260Z","dependency_job_id":"3f9040d4-84b1-402a-8c5e-2c0c9ffaafe6","html_url":"https://github.com/lerouxrgd/recloser","commit_stats":{"total_commits":46,"total_committers":4,"mean_commits":11.5,"dds":"0.32608695652173914","last_synced_commit":"8b3ecf4e0e09804a7905c78b57427185167864eb"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lerouxrgd%2Frecloser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lerouxrgd%2Frecloser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lerouxrgd%2Frecloser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lerouxrgd%2Frecloser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lerouxrgd","download_url":"https://codeload.github.com/lerouxrgd/recloser/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247318744,"owners_count":20919484,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","circuit-breaker","concurrent","rust"],"created_at":"2024-11-13T01:41:40.105Z","updated_at":"2025-04-05T10:08:38.430Z","avatar_url":"https://github.com/lerouxrgd.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# recloser \u0026emsp; [![latest]][crates.io] [![doc]][docs.rs]\n\n[latest]: https://img.shields.io/crates/v/recloser.svg\n[crates.io]: https://crates.io/crates/recloser\n[doc]: https://docs.rs/recloser/badge.svg\n[docs.rs]: https://docs.rs/recloser\n\nA concurrent [circuit breaker][cb] implemented with ring buffers.\n\nThe `Recloser` struct provides a `call(...)` method to wrap function calls that may\nfail, it will eagerly reject them when some `failure_rate` is reached, and it will allow\nthem again after some time.  A future aware version of `call(...)` is also available\nthrough an `AsyncRecloser` wrapper.\n\nThe API is largely based on [failsafe][] and the ring buffer implementation on\n[resilient4j][].\n\n[cb]: https://martinfowler.com/bliki/CircuitBreaker.html\n[failsafe]: https://github.com/dmexe/failsafe-rs\n[resilient4j]: https://resilience4j.readme.io/docs/circuitbreaker\n\n## Usage\n\nThe `Recloser` can be in three states:\n - `State::Closed(RingBuffer(len))`: The initial `Recloser`'s state. At least `len`\n    calls will be performed before calculating a `failure_rate` based on which\n    transitions to `State::Open(_)` state may happen.\n - `State::Open(duration)`: All calls will return `Err(Error::Rejected)` until\n    `duration` has elapsed, then transition to `State::HalfOpen(_)` state will happen.\n - `State::HalfOpen(RingBuffer(len))`: At least `len` calls will be performed before\n    calculating a `failure_rate` based on which transitions to either `State::Closed(_)`\n    or `State::Open(_)` states will happen.\n\nThe state transition settings can be customized as follows:\n\n ```rust\nuse std::time::Duration;\nuse recloser::Recloser;\n\n// Equivalent to Recloser::default()\nlet recloser = Recloser::custom()\n    .error_rate(0.5)\n    .closed_len(100)\n    .half_open_len(10)\n    .open_wait(Duration::from_secs(30))\n    .build();\n```\n\nWrapping dangerous function calls in order to control failure propagation:\n\n```rust\nuse recloser::{Recloser, Error};\n\n// Performs 1 call before calculating failure_rate\nlet recloser = Recloser::custom().closed_len(1).build();\n\nlet f1 = || Err::\u003c(), usize\u003e(1);\n\n// First call, just recorded as an error\nlet res = recloser.call(f1);\nassert!(matches!(res, Err(Error::Inner(1))));\n\n// Now also computes failure_rate, that is 100% here\n// Will transition to State::Open afterward\nlet res = recloser.call(f1);\nassert!(matches!(res, Err(Error::Inner(1))));\n\nlet f2 = || Err::\u003c(), i64\u003e(-1);\n\n// All calls are rejected (while in State::Open)\nlet res = recloser.call(f2);\nassert!(matches!(res, Err(Error::Rejected)));\n```\n\nIt is also possible to discard some errors on a per call basis.\nThis behavior is controlled by the `ErrorPredicate\u003cE\u003e`trait, which is already\nimplemented for all `Fn(\u0026E) -\u003e bool`.\n\n```rust\nuse recloser::{Recloser, Error};\n\nlet recloser = Recloser::default();\n\nlet f = || Err::\u003c(), usize\u003e(1);\n\n// Custom predicate that doesn't consider usize values as errors\nlet p = |_: \u0026usize| false;\n\n// Will not record resulting Err(1) as an error\nlet res = recloser.call_with(p, f);\nassert!(matches!(res, Err(Error::Inner(1))));\n```\n\nWrapping functions that return `Future`s requires to use an `AsyncRecloser` that just\nwraps a regular `Recloser`.\n\n```rust\nuse std::future;\nuse recloser::{Recloser, Error, AsyncRecloser};\n\nlet recloser = AsyncRecloser::from(Recloser::default());\n\nlet future = future::ready::\u003cResult\u003c(), usize\u003e\u003e(Err(1));\nlet future = recloser.call(future);\n```\n\n## Performances\n\nBenchmarks for `Recloser` and `failsafe::CircuitBreaker`\n- Single threaded workload: same performances\n- Multi threaded workload: `Recloser` has **10x** better performances\n\n```sh\nrecloser_simple         time:   [355.17 us 358.67 us 362.52 us]\nfailsafe_simple         time:   [403.47 us 406.90 us 410.29 us]\nrecloser_concurrent     time:   [668.44 us 674.26 us 680.48 us]\nfailsafe_concurrent     time:   [11.523 ms 11.613 ms 11.694 ms]\n```\n\nThese benchmarks were run on a `Intel Core i7-6700HQ @ 8x 3.5GHz` CPU.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flerouxrgd%2Frecloser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flerouxrgd%2Frecloser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flerouxrgd%2Frecloser/lists"}