Files

123 lines
2.3 KiB
Go

package nixmsg
import (
"math/rand"
"sync"
"time"
)
// reconnectBackoff 按 DEVELOPMENT 第 9 节:1s 起、加倍、上限 30s、±30% 抖动;
// 稳定在线 60s 后恢复到 1s。
type reconnectBackoff struct {
mu sync.Mutex
base time.Duration
onlineAt time.Time
online bool
stable bool
timer *time.Timer
}
func newReconnectBackoff() *reconnectBackoff {
return &reconnectBackoff{base: time.Second}
}
func (b *reconnectBackoff) Func(attempt int) time.Duration {
b.mu.Lock()
defer b.mu.Unlock()
if attempt <= 0 {
return 0
}
d := b.base
for i := 1; i < attempt; i++ {
d *= 2
if d > 30*time.Second {
d = 30 * time.Second
break
}
}
return withJitter(d)
}
func (b *reconnectBackoff) MarkOnline() {
b.mu.Lock()
defer b.mu.Unlock()
b.online = true
b.onlineAt = time.Now()
b.stable = false
if b.timer != nil {
b.timer.Stop()
}
b.timer = time.AfterFunc(60*time.Second, func() {
b.mu.Lock()
defer b.mu.Unlock()
if b.online {
b.stable = true
b.base = time.Second
}
})
}
func (b *reconnectBackoff) MarkOffline() {
b.mu.Lock()
defer b.mu.Unlock()
if b.timer != nil {
b.timer.Stop()
b.timer = nil
}
wasOnline := b.online
b.online = false
if !wasOnline {
// 连接尚未成功就失败:在 Func 内已按 attempt 加倍,这里把 base 提到下次周期的起点。
next := b.base * 2
if next > 30*time.Second {
next = 30 * time.Second
}
if next < time.Second {
next = time.Second
}
b.base = next
return
}
if b.stable || time.Since(b.onlineAt) >= 60*time.Second {
b.base = time.Second
b.stable = false
return
}
next := b.base * 2
if next > 30*time.Second {
next = 30 * time.Second
}
b.base = next
b.stable = false
}
func (b *reconnectBackoff) Base() time.Duration {
b.mu.Lock()
defer b.mu.Unlock()
return b.base
}
func withJitter(d time.Duration) time.Duration {
// ±30%
f := 0.7 + rand.Float64()*0.6
return time.Duration(float64(d) * f)
}
// computeBackoffDelay 供单测:无 attempt 与 base 计算无抖动前的标称延迟。
func computeBackoffDelay(base time.Duration, attempt int) time.Duration {
if attempt <= 0 {
return 0
}
d := base
for i := 1; i < attempt; i++ {
d *= 2
if d > 30*time.Second {
return 30 * time.Second
}
}
if d > 30*time.Second {
return 30 * time.Second
}
return d
}