Documentation
¶
Overview ¶
Package breaker stops calling a dependency that is clearly down.
The problem it solves is not the failing dependency — it is what your service does about it. Every request waits for a three-second timeout, so every worker is blocked waiting, so the service stops answering the endpoints that do not need that dependency at all. One failing downstream has taken out everything.
An open breaker fails those calls immediately, with KindUnavailable, which frees the workers and gives the dependency room to recover.
closed calls pass. Consecutive failures count toward the threshold. open calls fail at once. After Cooldown, one probe is allowed. half-open one call probes. Success closes; failure re-opens.
Example ¶
After enough consecutive failures the breaker opens and stops calling the dependency at all, which frees the workers it was blocking.
package main
import (
"context"
"fmt"
"time"
"github.com/IncepTools/go/src/core/errs"
"github.com/IncepTools/go/src/reliability/breaker"
)
func main() {
b := breaker.New(breaker.Options{
Name: "pricing-api", Threshold: 3, Cooldown: time.Minute,
})
ctx := context.Background()
for range 3 {
_ = b.Do(ctx, func(context.Context) error {
return errs.Unavailable("connection refused")
})
}
fmt.Println("state:", b.State())
called := false
err := b.Do(ctx, func(context.Context) error { called = true; return nil })
fmt.Println("dependency called:", called)
fmt.Println("code:", errs.CodeOf(err))
}
Output: state: open dependency called: false code: CIRCUIT_OPEN
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Breaker ¶
type Breaker struct {
// contains filtered or unexported fields
}
Breaker guards one dependency. Create one per dependency, not one per service: a shared breaker lets an unrelated failure block a healthy call.
func New ¶
New builds a breaker.
Example (ClientErrorsDoNotCount) ¶
A 404 from a dependency is a perfectly healthy answer. Counting it would mean a client asking for missing things repeatedly takes the dependency out for everyone.
package main
import (
"context"
"fmt"
"time"
"github.com/IncepTools/go/src/core/errs"
"github.com/IncepTools/go/src/reliability/breaker"
)
func main() {
b := breaker.New(breaker.Options{Threshold: 2, Cooldown: time.Minute})
for range 10 {
_ = b.Do(context.Background(), func(context.Context) error {
return errs.NotFound("no price for that SKU")
})
}
fmt.Println(b.State())
}
Output: closed
type Options ¶
type Options struct {
// Name identifies the dependency in logs and in the error.
Name string
// Threshold is the consecutive failures that open the breaker. Default 5.
Threshold int
// Cooldown is how long it stays open before probing. Default 30s.
Cooldown time.Duration
// Counts decides whether an error counts toward the threshold.
//
// The default counts only unavailability and timeouts. A 404 from a
// dependency is a perfectly healthy answer and must not open a breaker —
// counting it means a client asking for missing things repeatedly takes
// the dependency out for everyone.
Counts func(error) bool
// OnStateChange is called on every transition.
OnStateChange func(from, to State)
}
Options configures a breaker.
type State ¶
type State string
State is the breaker's position.
const ( // Closed is normal operation: calls pass, and consecutive failures count // toward the threshold. Closed State = "closed" // Open is failing fast: calls are refused without touching the // dependency, which is what frees the workers it was blocking. Open State = "open" // HalfOpen is one probe in flight. Success closes the breaker; failure // re-opens it. HalfOpen State = "half-open" )