mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-11 00:09:31 +00:00
96ae1d45e0
- [x] Add golangci-lint configuration with gocritic ifElseChain disabled
- [x] Rename error variables to avoid shadowing (createErr, watcherErr, watchErr, etc.)
- [x] Replace `interface{}` with `any` type alias throughout codebase
- [x] Add package-level documentation comments to all internal packages
- [x] Reorder struct fields alphabetically for consistency
- [x] Extract UI constants (terminal dimensions, column widths, colors) to constants.go
- [x] Refactor BubbleTeaUI main view rendering into smaller helper functions
- [x] Simplify nested conditionals and improve code clarity
- [x] Add `isForwardDisabled()` helper method to BubbleTeaUI
- [x] Update file permissions from 0644 to 0600 in config tests
- [x] Add `#nosec` comments and error suppression where appropriate
- [x] Improve test table struct field ordering for readability
- [x] Fix resource parsing in AddForward using strings.SplitN
- [x] Add comprehensive tests for new UI helper functions and constants
96 lines
2.6 KiB
Go
96 lines
2.6 KiB
Go
// Package retry provides exponential backoff with jitter for retry logic.
|
|
// It implements a backoff sequence of 1s → 2s → 4s → 8s → 10s (max),
|
|
// with 10% random jitter to prevent thundering herd problems.
|
|
//
|
|
// Basic usage:
|
|
//
|
|
// backoff := retry.NewBackoff()
|
|
// for {
|
|
// err := doSomething()
|
|
// if err == nil {
|
|
// backoff.Reset()
|
|
// break
|
|
// }
|
|
// delay := backoff.Next()
|
|
// time.Sleep(delay)
|
|
// }
|
|
package retry
|
|
|
|
import (
|
|
"math"
|
|
"math/rand"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// Backoff intervals: 1s → 2s → 4s → 8s → 10s (max)
|
|
initialDelay = 1 * time.Second
|
|
maxDelay = 10 * time.Second
|
|
jitterPct = 0.1 // 10% jitter
|
|
// maxAttempt caps the exponent to prevent math.Pow overflow
|
|
// 2^30 seconds is ~34 years, well above maxDelay, so this is safe
|
|
maxAttempt = 30
|
|
)
|
|
|
|
// Backoff implements exponential backoff with jitter for retry logic.
|
|
// The backoff sequence is: 1s → 2s → 4s → 8s → 10s (max, then stays at 10s).
|
|
type Backoff struct {
|
|
rng *rand.Rand
|
|
attempt int
|
|
}
|
|
|
|
// NewBackoff creates a new Backoff instance with a seeded random number generator.
|
|
func NewBackoff() *Backoff {
|
|
return &Backoff{
|
|
attempt: 0,
|
|
// #nosec G404 -- math/rand is appropriate for backoff jitter; cryptographic randomness not needed
|
|
rng: rand.New(rand.NewSource(time.Now().UnixNano())),
|
|
}
|
|
}
|
|
|
|
// Next returns the next backoff duration and increments the attempt counter.
|
|
// The duration follows exponential backoff: 1s → 2s → 4s → 8s → 10s (max).
|
|
// A 10% jitter is added to prevent thundering herd effects.
|
|
func (b *Backoff) Next() time.Duration {
|
|
// Cap attempt to prevent overflow in math.Pow
|
|
attempt := b.attempt
|
|
if attempt > maxAttempt {
|
|
attempt = maxAttempt
|
|
}
|
|
|
|
// Calculate base delay: 2^attempt seconds
|
|
exp := math.Pow(2, float64(attempt))
|
|
delay := time.Duration(exp) * time.Second
|
|
|
|
// Cap at max delay
|
|
if delay > maxDelay {
|
|
delay = maxDelay
|
|
}
|
|
|
|
// Add jitter (±10%)
|
|
jitter := b.calculateJitter(delay)
|
|
delay += jitter
|
|
|
|
b.attempt++
|
|
return delay
|
|
}
|
|
|
|
// Reset resets the backoff to the initial state.
|
|
func (b *Backoff) Reset() {
|
|
b.attempt = 0
|
|
}
|
|
|
|
// Attempt returns the current attempt number.
|
|
func (b *Backoff) Attempt() int {
|
|
return b.attempt
|
|
}
|
|
|
|
// calculateJitter adds random jitter to prevent synchronized retries.
|
|
// Returns a value between -jitterPct*delay and +jitterPct*delay.
|
|
func (b *Backoff) calculateJitter(delay time.Duration) time.Duration {
|
|
maxJitter := float64(delay) * jitterPct
|
|
// Generate random value in range [-maxJitter, +maxJitter]
|
|
jitter := (b.rng.Float64()*2 - 1) * maxJitter
|
|
return time.Duration(jitter)
|
|
}
|