Files
go-honeybee/transport/retry.go
T
Jay b44a46ed2f Migrate logging to go-mana-component; delete logging/ package
Replaces the flat key-value logging scheme with component-based structured
logging via go-mana-component. Each layer (pool, worker, connection) builds
its own component identity and derives a *slog.Logger from a caller-supplied
slog.Handler.

- Delete logging/ package (logging.go, logging_test.go)
- Strip LoggingEnabled and LogLevel from ConnectionConfig, PoolConfig,
 WorkerConfig; remove associated option funcs
- Change NewConnection and NewConnectionFromSocket to accept ctx and
 slog.Handler instead of *slog.Logger; constructors build component
 identity via MustNew/MustExtend internally
- Change WorkerFactory, NewWorker, connect, and RunDialer to carry
 slog.Handler; remove PoolPlugin.Handler
- Change NewPool to establish pool component identity via MustNew;
 remove pool_id field, PoolPlugin.ID, and ErrInvalidPoolID
- Fix data race in MockSlogHandler: WithAttrs now shares parent mutex
 pointer rather than allocating a new one per child
- Run go fix
2026-05-20 13:04:58 -04:00

83 lines
1.8 KiB
Go

package transport
import (
"math"
"math/rand"
"time"
)
type RetryManager struct {
config *RetryConfig
retryCount int
saturation int
}
func NewRetryManager(config *RetryConfig) *RetryManager {
// saturationCount: retry count at which base delay meets or exceeds MaxDelay.
// Conservative by two to preserve jitter variance near the boundary.
saturation := 0
if config != nil &&
config.InitialDelay > 0 &&
config.InitialDelay <= config.MaxDelay {
ratio := float64(config.MaxDelay) / float64(config.InitialDelay)
saturation = int(math.Ceil(math.Log2(ratio))) + 2
}
return &RetryManager{
config: config,
retryCount: 0,
saturation: saturation,
}
}
func (r *RetryManager) ShouldRetry() bool {
if r.config == nil {
return false
}
if r.config.MaxRetries > 0 && r.retryCount >= r.config.MaxRetries {
return false
}
return true
}
func (r *RetryManager) CalculateDelay() time.Duration {
if r.config == nil {
return time.Second
}
// First attempt: immediate retry
if r.retryCount == 0 {
return 0
}
// if saturation is reached, calculated backoff will always be higher than
// the maximum delay
if r.config != nil && r.retryCount >= r.saturation {
return r.config.MaxDelay
}
// Exponential backoff: InitialDelay * 2^(attempts-1)
shift := min(r.retryCount-1, 62) // prevent overflow
backoffMultiplier := float64(int64(1) << shift)
baseDelay := float64(r.config.InitialDelay) * backoffMultiplier
// Apply jitter: delay * (1 + jitterFactor * (random - 0.5))
random := rand.Float64()
jitterMultiplier := 1 + r.config.JitterFactor*(random-0.5)
delay := min(
// Cap at MaxDelay
time.Duration(baseDelay*jitterMultiplier), r.config.MaxDelay)
return delay
}
func (m *RetryManager) RecordRetry() {
m.retryCount++
}
func (m *RetryManager) RetryCount() int {
return m.retryCount
}