b44a46ed2f
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
83 lines
1.8 KiB
Go
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
|
|
}
|