mirror of
https://github.com/TronoSfera/backupy-agent.git
synced 2026-05-18 18:13:30 +03:00
Source ports from the TronoSfera/backupy-cloud monorepo:
- apps/agent/ — Go agent (WSS client, persistent queue, Docker
discovery, 5 DB drivers: PG/MySQL/Mongo/Redis/SQLite,
pre/post hooks, Prometheus metrics)
- apps/backupy-decrypt/ — standalone CLI for client-side decryption
- packages/proto/ — protobuf wire format (generated .pb.go committed
so the repo builds without protoc)
- docs/ — agent spec + wire-protocol contract
Apache-2.0 license. Image published to ghcr.io/tronosfera/backupy-agent
on every v* tag via .github/workflows/release.yml (multi-arch amd64+arm64).
61 lines
1.5 KiB
Go
61 lines
1.5 KiB
Go
package wss
|
||
|
||
import (
|
||
"math/rand"
|
||
"time"
|
||
)
|
||
|
||
// Backoff implements the exponential schedule specified in
|
||
// docs/03-agent-spec.md → "Reconnect: exponential backoff 1s → 2s → 4s …
|
||
// → 60s cap, jitter ±20%".
|
||
//
|
||
// The struct is safe for use from a single goroutine. The reconnect loop
|
||
// is single-threaded, so no mutex is needed.
|
||
type Backoff struct {
|
||
Initial time.Duration
|
||
Max time.Duration
|
||
Factor float64
|
||
JitterPC float64 // 0.2 → ±20%
|
||
|
||
current time.Duration
|
||
rng *rand.Rand
|
||
}
|
||
|
||
// NewBackoff returns a Backoff with spec defaults (1s → 60s, ±20%, ×2).
|
||
func NewBackoff() *Backoff {
|
||
return &Backoff{
|
||
Initial: 1 * time.Second,
|
||
Max: 60 * time.Second,
|
||
Factor: 2.0,
|
||
JitterPC: 0.2,
|
||
// Deterministic-ish but unique per agent: seed from the wall clock.
|
||
rng: rand.New(rand.NewSource(time.Now().UnixNano())), //nolint:gosec // not a security context
|
||
}
|
||
}
|
||
|
||
// Next returns the next delay and advances the schedule.
|
||
func (b *Backoff) Next() time.Duration {
|
||
if b.current == 0 {
|
||
b.current = b.Initial
|
||
} else {
|
||
b.current = time.Duration(float64(b.current) * b.Factor)
|
||
if b.current > b.Max {
|
||
b.current = b.Max
|
||
}
|
||
}
|
||
return b.withJitter(b.current)
|
||
}
|
||
|
||
// Reset rewinds the schedule. Call on a clean reconnect.
|
||
func (b *Backoff) Reset() {
|
||
b.current = 0
|
||
}
|
||
|
||
func (b *Backoff) withJitter(d time.Duration) time.Duration {
|
||
if b.JitterPC <= 0 {
|
||
return d
|
||
}
|
||
// pick uniformly in [-jitter, +jitter]
|
||
jitter := b.JitterPC * (b.rng.Float64()*2 - 1)
|
||
return time.Duration(float64(d) * (1 + jitter))
|
||
}
|