Open-source project
cep21/circuit avatar
cep21/circuit

Circuit: a circuit breaker for Go that speaks Hystrix

An efficient and feature complete Hystrix like Go implementation of the circuit breaker pattern.

815 stars51 forksGoApache-2.0

At a glance

What is it?
Circuit is a Go implementation of the circuit breaker pattern with no forced goroutines, context integration, expvar health reporting and SLO tracking, and it ships a Hystrix-compatible closer so the familiar Netflix dashboard semantics work unchanged. Its test suite is the more interesting artefact, with property, regression, stress and fuzz tests driven from the Makefile.
Who is it for?
Adopt Circuit if you are writing a Go service that calls a dependency which can hang or fail, because the failure mode it prevents is the one that takes your own callers down with it, and the implementation avoids the goroutine and timeout machinery that most circuit breaker libraries impose. Do not adopt it expecting a drop-in for a particular library's API, because the upgrade guide for the move from v3 to v4 exists precisely because the surface changed.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A hanging dependency takes your service with it

The README's summary of what a circuit is for is worth reading in full, because the first case is the one that motivates the pattern.

A downstream service failed and all requests hang forever, and without a circuit your service would also hang forever. Because you have a circuit, you detect this failure quickly and can return errors quickly while waiting for the downstream service to recover.

The rest of the reasons are about the operational edges of a dependency. Circuits make monitoring and metrics boundaries, creating common metric names for the common downstream failure types, and this package goes further to formalize that in an SLO tracking pattern. They create a common place for downstream failure fallback logic. Downstream services sometimes fail entirely when overloaded, and while in a degraded state circuits allow you to push downstream services to the edge between absolute failure and mostly working. The open or closed state of a circuit is a clear early warning sign of downstream failures. And circuits allow you to protect your dependencies from abnormal rushes of traffic.

That last point is the one that is easy to miss. A circuit breaker is usually described as protection for your service, but the README points out that it protects the dependency too, by refusing to pile more traffic onto something that is already failing.

The implementation is described as an efficient and feature complete Hystrix like Go implementation of the circuit breaker pattern. The project is Go, Apache-2.0 licensed, on the master branch, with the last push on 2026-10-16. The releases are v4.0.0 in January 2024, v4.1.0 in February 2026 and v4.2.0 in August 2026.

A manager, a circuit, and two callbacks

The hello world example is the whole API in a dozen lines.

go
// Manages all our circuits
h := circuit.Manager{}
// Create a circuit with a unique name
c := h.MustCreateCircuit("hello-world")
// Call the circuit
errResult := c.Execute(context.Background(), func(ctx context.Context) error {
  return nil
}, nil)
fmt.Println("Result of execution:", errResult)
// Output: Result of execution: <nil>

A Manager holds your circuits, MustCreateCircuit creates one by name, and Execute takes the work as a function that receives a context and returns an error, plus a fallback function that is nil in this example.

The fallback is the second half of the API and the example for it is just as short. You can create a circuit without the manager, through NewCircuitFromConfig, and pass a fallback that turns the failure into something else:

go
c := circuit.NewCircuitFromConfig("hello-world-fallback", circuit.Config{})
errResult := c.Execute(context.Background(), func(ctx context.Context) error {
	return errors.New("this will fail")
}, func(ctx context.Context, err error) error {
	fmt.Println("Circuit failed with error, but fallback returns nil")
	return nil
})

So the shape of a call site is always the same: the work, the fallback, and the error that comes back. There is no separate object to manage per request, and the manager exists so that a circuit keeps its state across calls, which is the whole point of the pattern.

Execute or Go, and the context doing the work

Two things in the feature list change how the library feels in a codebase: no forced goroutines and integration with context.Context. The third example shows what that means in practice.

It is recommended to use circuit.Execute and a context aware function. If, however, you want to exit your run function early and leave it hanging, possibly forever, then you can call circuit.Go:

go
h := circuit.Manager{}
c := h.MustCreateCircuit("untrusting-circuit", circuit.Config{
  Execution: circuit.ExecutionConfig{
    // Time out the context after a few ms
    Timeout: time.Millisecond * 30,
  },
})

errResult := c.Go(context.Background(), func(ctx context.Context) error {
  // Sleep 30 seconds, way longer than our timeout
  time.Sleep(time.Second * 30)
  return nil
}, nil)
fmt.Printf("err=%v", errResult)
// Output: err=context deadline exceeded

The distinction is a real design decision rather than a convenience. Execute runs the work and the timeout is enforced around it, and Go is for the case where the work is allowed to keep running past the point where the caller has given up, with the circuit returning on the timeout and the abandoned work still in flight. A library that always spawns a goroutine forces the second behaviour on everybody, and a library that never does forces the first, and this one lets you choose per circuit.

The example also shows the failure being surfaced as a context deadline exceeded rather than as a timeout error of the library's own, which keeps the error vocabulary in your service the same as the rest of your context-aware code.

Hystrix semantics, when you already have Hystrix dashboards

If you are migrating from a Hystrix setup, the configuration is in the closers/hystrix package and is well documented on the Hystrix wiki, and the feature list also promises support for the Netflix Hystrix dashboards, even with custom circuit transition logic.

The configuration object is a factory with an opener and a closer, and the opener carries a request volume threshold, the number of requests the circuit waits for before it checks whether it can close again:

go
configuration := hystrix.Factory{
  // Hystrix open logic is to open the circuit after an % of errors
  ConfigureOpener: hystrix.ConfigureOpener{
    // We change the default to wait for 10 requests, not 20, before checking to close
    RequestVolumeThreshold: 10,
    // The default values match what hystrix does by default
  },
  // Hystrix close logic is to sleep then check
  ConfigureCloser: hystrix.ConfigureCloser{
    // The default values match what hystrix does by default
  },
}

The factory is then handed to the manager as a default, so that every circuit the manager creates inherits it:

go
h := circuit.Manager{
  // Tell the manager to use this configuration factory whenever it makes a new circuit
  DefaultCircuitProperties: []circuit.CommandPropertiesConstructor{configuration.Configure},
}

All configuration parameters are documented in config.go, and the README says the circuit open and close logic configuration is documented with the logic, so the parameters live next to the behaviour they control rather than in a separate list.

The broader point is that this is not a Hystrix clone with a different name. The closer packages are the extension point, and the README also points at an additive increase and multiplicative decrease implementation and at a Prometheus metrics collector as separate projects that plug into the same idea.

Metrics on expvar, and SLO tracking

The feature list is long and the observability entries are the ones that change how you run the thing.

There is metric tracking, and the health and configuration of circuits can be exposed on expvar, which is Go's built-in debugging endpoint, so a circuit's state is readable without adding a metrics library to your service. There is SLO tracking, which is the formalisation the summary of the pattern mentions, so the library tracks service level objectives for the calls it guards rather than only counting failures. There is support for the Netflix Hystrix dashboards, so the numbers your existing tooling expects still arrive, and there is a Prometheus metrics collector available as a separate project.

Dashboard metrics can be enabled with the MetricEventStream object, which uses rolling statistics to report circuit information, and the metrics are built on a rolling statistics package in the faststats directory. The rolling counter, the sorted durations percentile and the timed check all have fuzz targets, which is a hint about which parts of the library the author considered hardest to get right.

Other entries in the list are about behaviour rather than measurement. Recoverable panic handling, several error handling features, customizable state transition logic allowing complex circuit state changes, and live configuration changes, which means a circuit's thresholds can be adjusted without a restart.

There is also an entry about tooling that is easy to overlook: generatable interface wrapping support with the circuitgen project, which generates the interface wrappers for a new circuit type so you do not write them by hand.

The test suite is where the engineering went

The repository listing shows an unusual set of test files at the top level, and the Makefile turns them into a workflow.

There is circuit_test.go, then circuit_bench_test.go, circuit_property_test.go, circuit_regression_test.go and circuit_stress_test.go, and manager_test.go and manager_stress_test.go, plus closers_test.go, config_test.go, errors_test.go, example_test.go, expvar_test.go and gowrapper_test.go. Property tests, regression tests and stress tests as separate files is the shape of a library that treats concurrency as the main source of bugs, which is fair for something whose whole job is to arbitrate between goroutines.

The Makefile is explicit about which target to use when. The default target is the fast loop, build plus test plus lint, and the help text puts it at about five seconds. The ci target is everything continuous integration runs, build plus the race test plus lint, at about a minute, and the comment says to use it before submitting a pull request. The race target is not the default, and it runs the race detector with ten iterations and halt_on_error set, so a race stops the run rather than being reported at the end.

There is a cover target that writes an HTML coverage report, a bench target that runs the benchmarks with allocation reporting, and a fix target that runs gofmt and goimports. And there is active fuzzing, one target at a time, with a default time budget of thirty seconds per target and a list of the targets in the Makefile: the rolling counter operations, the rolling counter JSON, the sorted durations percentile, the rolling bucket advance and the timed check JSON.

For a library whose correctness argument is about timing, that is the right set of tools.

Versions, the upgrade path and one small dependency

The dependency list is almost empty, which for a concurrency library is a point in its favour. The go.mod names the module github.com/cep21/circuit/v4, targets Go 1.24, and requires one direct dependency, testify. Everything else is indirect and is there because the test framework needs it.

The versioning follows the semantic conventions with a major bump, and the README points at UPGRADE_GUIDE.md for the move from v3 to v4. A library that has had a v4 major release has changed something that users notice, and the guide is the place to find out what.

The recent release history is v4.0.0 in January 2024, described as the v4 major release, v4.1.0 in February 2026 and v4.2.0 in August 2026, with the last push on 2026-10-16.

One documentation note is worth passing on. The README says there are a large number of examples on the godoc that are worth looking at, and that they tend to be more up to date than the README document. In a library with this much surface, the executable examples are the more accurate reference, and the README is the summary.

The mascot image at the top of the README is credited in a comment to Jack Lindamood, under a Creative Commons 3.0 attribution licence, and the project also carries a GoDoc badge, a coverage badge and a Go Report Card badge, so the quality signals are the automatic kind rather than a claim in the prose.

Editorial conclusion

Adopt Circuit if you are writing a Go service that calls a dependency which can hang or fail, because the failure mode it prevents is the one that takes your own callers down with it, and the implementation avoids the goroutine and timeout machinery that most circuit breaker libraries impose. Do not adopt it expecting a drop-in for a particular library's API, because the upgrade guide for the move from v3 to v4 exists precisely because the surface changed. Before you wire it in, read the SLO tracking and the expvar output, since that is where the operational value is, and if you are migrating from Hystrix itself, use the closers/hystrix configuration so the open and close thresholds match what your dashboards already assume.

Frequently asked questions

What problem does the Circuit library solve?

It implements the circuit breaker pattern so that a downstream service which fails or hangs does not take the calling service down with it. The README argues that without a circuit your service would also hang forever, and that a circuit lets you detect the failure quickly and return errors quickly while the dependency recovers.

What is the difference between circuit.Execute and circuit.Go?

Execute is recommended, with a context aware function and the timeout enforced around the call. Go is for the case where you want to exit your run function early and leave it hanging, possibly forever, so the circuit returns on the timeout while the abandoned work continues in the background.

Does Circuit support Hystrix configuration and dashboards?

Yes. The open and close logic is configured through the closers/hystrix package, documented on the Hystrix wiki, and the feature list promises support for the Netflix Hystrix dashboards even with custom circuit transition logic. The factory is passed to the manager as a default so new circuits inherit it.

How do I inspect the state of a circuit at run time?

Circuit health and configuration can be exposed on expvar, Go's built-in debugging endpoint. The package also offers metric tracking, SLO tracking, a MetricEventStream object for dashboard metrics using rolling statistics, and a Prometheus metrics collector maintained as a separate project.

Official sources

  1. cep21/circuit on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cep21-circuit.svg)](https://hysenlabs.com/projects/cep21-circuit)

Was this useful?