Skip to content

Commit da2cd50

Browse files
committed
fix(health): avoid 32-bit circuit breaker overflow
1 parent 13da5f0 commit da2cd50

File tree

4 files changed

+12
-52
lines changed

4 files changed

+12
-52
lines changed

internal/health/circuit.go

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package health
44
import (
55
"context"
66
"errors"
7-
"math"
87

98
"github.com/rs/zerolog"
109
"github.com/sony/gobreaker/v2"
@@ -31,18 +30,8 @@ type CircuitBreaker struct {
3130
// If logger is non-nil, state transitions are logged (Info level, Warn when the breaker opens).
3231
// The breaker treats a nil error and context.Canceled as successful outcomes.
3332
func NewCircuitBreaker(name string, cfg CircuitBreakerConfig, logger *zerolog.Logger) *CircuitBreaker {
34-
// Get config values with safe uint32 conversion
35-
halfOpenProbes := cfg.GetHalfOpenProbes()
36-
if halfOpenProbes < 0 {
37-
halfOpenProbes = DefaultHalfOpenProbes
38-
}
39-
failureThreshold := cfg.GetFailureThreshold()
40-
if failureThreshold < 0 {
41-
failureThreshold = DefaultFailureThreshold
42-
}
43-
44-
maxRequests := safeUint32(halfOpenProbes)
45-
failureLimit := safeUint32(failureThreshold)
33+
maxRequests := cfg.GetHalfOpenProbes()
34+
failureLimit := cfg.GetFailureThreshold()
4635

4736
settings := gobreaker.Settings{
4837
Name: name,
@@ -76,16 +65,6 @@ func NewCircuitBreaker(name string, cfg CircuitBreakerConfig, logger *zerolog.Lo
7665
}
7766
}
7867

79-
func safeUint32(value int) uint32 {
80-
if value <= 0 {
81-
return 0
82-
}
83-
if value > math.MaxUint32 {
84-
return math.MaxUint32
85-
}
86-
return uint32(value)
87-
}
88-
8968
// Allow checks if a request is allowed through the circuit breaker.
9069
func (c *CircuitBreaker) Allow() (done func(err error), err error) {
9170
d, err := c.cb.Allow()

internal/health/config.go

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,14 @@ const (
2222

2323
// CircuitBreakerConfig defines circuit breaker behavior.
2424
type CircuitBreakerConfig struct {
25-
// FailureThreshold is the number of consecutive failures before opening the circuit.
26-
// Default: 5
27-
FailureThreshold int `yaml:"failure_threshold" toml:"failure_threshold"`
28-
29-
// OpenDurationMS is the duration in milliseconds the circuit stays open before
30-
// transitioning to half-open state. Default: 30000 (30 seconds)
31-
OpenDurationMS int `yaml:"open_duration_ms" toml:"open_duration_ms"`
32-
33-
// HalfOpenProbes is the number of probe requests allowed in half-open state.
34-
// If all probes succeed, circuit closes. If any fails, circuit reopens.
35-
// Default: 3
36-
HalfOpenProbes int `yaml:"half_open_probes" toml:"half_open_probes"`
25+
OpenDurationMS int `yaml:"open_duration_ms" toml:"open_duration_ms"`
26+
FailureThreshold uint32 `yaml:"failure_threshold" toml:"failure_threshold"`
27+
HalfOpenProbes uint32 `yaml:"half_open_probes" toml:"half_open_probes"`
3728
}
3829

3930
// GetFailureThreshold returns the configured failure threshold or default 5.
40-
func (c *CircuitBreakerConfig) GetFailureThreshold() int {
41-
if c.FailureThreshold <= 0 {
31+
func (c *CircuitBreakerConfig) GetFailureThreshold() uint32 {
32+
if c.FailureThreshold == 0 {
4233
return DefaultFailureThreshold
4334
}
4435
return c.FailureThreshold
@@ -54,8 +45,8 @@ func (c *CircuitBreakerConfig) GetOpenDuration() time.Duration {
5445
}
5546

5647
// GetHalfOpenProbes returns the configured half-open probes or default 3.
57-
func (c *CircuitBreakerConfig) GetHalfOpenProbes() int {
58-
if c.HalfOpenProbes <= 0 {
48+
func (c *CircuitBreakerConfig) GetHalfOpenProbes() uint32 {
49+
if c.HalfOpenProbes == 0 {
5950
return DefaultHalfOpenProbes
6051
}
6152
return c.HalfOpenProbes

internal/health/config_test.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func TestCircuitBreakerConfigGetFailureThreshold(t *testing.T) {
1111
tests := []struct {
1212
name string
1313
config CircuitBreakerConfig
14-
expected int
14+
expected uint32
1515
}{
1616
{
1717
name: "zero value returns default 5",
@@ -28,11 +28,6 @@ func TestCircuitBreakerConfigGetFailureThreshold(t *testing.T) {
2828
config: CircuitBreakerConfig{FailureThreshold: 1},
2929
expected: 1,
3030
},
31-
{
32-
name: "negative value returns default 5",
33-
config: CircuitBreakerConfig{FailureThreshold: -1},
34-
expected: 5,
35-
},
3631
}
3732

3833
for _, tt := range tests {
@@ -93,7 +88,7 @@ func TestCircuitBreakerConfigGetHalfOpenProbes(t *testing.T) {
9388
tests := []struct {
9489
name string
9590
config CircuitBreakerConfig
96-
expected int
91+
expected uint32
9792
}{
9893
{
9994
name: "zero value returns default 3",
@@ -110,11 +105,6 @@ func TestCircuitBreakerConfigGetHalfOpenProbes(t *testing.T) {
110105
config: CircuitBreakerConfig{HalfOpenProbes: 1},
111106
expected: 1,
112107
},
113-
{
114-
name: "negative value returns default 3",
115-
config: CircuitBreakerConfig{HalfOpenProbes: -2},
116-
expected: 3,
117-
},
118108
}
119109

120110
for _, tt := range tests {

internal/proxy/handler_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func serveMessages(t *testing.T, handler http.Handler) *httptest.ResponseRecorde
111111
func newTrackedHandler(
112112
t *testing.T,
113113
providerName, backendURL, routerName string,
114-
failureThreshold int,
114+
failureThreshold uint32,
115115
) (*Handler, *health.Tracker) {
116116
t.Helper()
117117

0 commit comments

Comments
 (0)