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 }