Files
onvif/event/stream/jitter_test.go
Sebastian Norling 4fd92dd229 feat(event/stream): raise recreate backoff cap and add jitter
The previous 30-second cap meant a 1000-camera fleet recovering from
a switch reboot would generate a sustained 33 RPS of doomed
CreatePullPointSubscription traffic against still-booting cameras,
and the synchronised retries would arrive in phase. Two changes:

* Cap raised to 5 minutes. Single-camera recovery latency goes from
  '<=30s after camera comes back' to '<=300s', which is fine because
  by the time we are this deep in backoff the camera has already been
  unreachable through 6+ attempts (1s, 2s, 4s, 8s, 16s, 30s under the
  old cap) — the marginal recovery delay is acceptable to avoid the
  network melt.

* Symmetric ±25% jitter on every recreate sleep so synchronised
  drops (switch reboot, DHCP storm, NTP slew) do not cause
  synchronised reconnect surges. Standard practice — same shape AWS,
  Cloudflare and HA event_manager use.

Tests assert the jitter range, the documented cap value (so a future
maintainer flipping it back to 30s notices in CI), and that jitter
varies across calls (proves the rand source is wired).
2026-05-21 14:59:44 +02:00

45 lines
1.2 KiB
Go

package stream
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestJitter_StaysWithinFraction(t *testing.T) {
const base = time.Second
low := time.Duration(float64(base) * (1 - jitterFraction))
high := time.Duration(float64(base) * (1 + jitterFraction))
for i := 0; i < 200; i++ {
got := jitter(base)
assert.GreaterOrEqual(t, got, low, "iteration %d", i)
assert.LessOrEqual(t, got, high, "iteration %d", i)
}
}
func TestJitter_ZeroAndNegativeReturnPositive(t *testing.T) {
assert.Greater(t, jitter(0), time.Duration(0))
assert.Greater(t, jitter(-time.Second), time.Duration(0))
}
func TestJitter_VariesAcrossCalls(t *testing.T) {
// Sanity check that we're not returning a constant. Vanishingly
// unlikely to flake (probability ~ (1/uint64-space)^9).
first := jitter(time.Second)
allEqual := true
for i := 0; i < 10; i++ {
if jitter(time.Second) != first {
allEqual = false
break
}
}
assert.False(t, allEqual, "jitter is producing a constant; rand seed not working")
}
func TestMaxRecreateBackoff_Is5Minutes(t *testing.T) {
// Document the policy choice in a test so a future maintainer
// changing this notices.
assert.Equal(t, 5*time.Minute, maxRecreateBackoff)
}