Carom
Retries, timeouts, circuit breakers and bulkheads for .NET, with the safe defaults already applied. Named after the billiards shot where the ball bounces before it reaches its target.
dotnet add package CaromcopyThe problem
Most resilience incidents are not caused by a missing retry. They are caused by a retry that was configured badly.
A retry loop without jitter turns a brief blip into a thundering herd: every client in the fleet backs off for the same interval, then hits the recovering service at the same instant. The resilience feature becomes the outage. Polly will happily let you write that policy.
Carom's answer is to not let you make the mistake. Decorrelated jitter is mandatory, not a flag you have to remember.
What it does
The API is named after the shot. Each pattern is a thing that happens to the ball:
Bounce | retry and timeout |
Cushion | circuit breaker |
Pocket | fallback |
Compartment | bulkhead isolation |
Throttle | token-bucket rate limiting |
They compose, rather than fighting each other.
using Carom;
// Retry with exponential backoff and jitter, which you do not have to ask for.
var result = await Carom.ShotAsync(() => api.CallAsync(), retries: 3);
// Retry with a timeout around it.
var bounce = Bounce.Times(5).WithTimeout(TimeSpan.FromSeconds(30));
var data = await Carom.ShotAsync(() => apiClient.FetchAsync(), bounce);- Zero dependencies in the core.
- 13KB core, with everything else opt-in.
- Under 100 bytes allocated on the hot path.
- Extensions for HTTP, ASP.NET Core health checks, EF Core retry and OpenTelemetry.
About the numbers
The repository's benchmarks put the hot path at roughly 15x faster than Polly v8 (10.9ns against 167.8ns). That is my benchmark on my hardware. The code is in the repo, so measure it yourself before you believe me.
Where it fits
Any call that leaves your process and might not come back: a third-party API, a database under load, a broker mid-failover.