From 677aec2a429d3a7937d630436e1448379c102f35 Mon Sep 17 00:00:00 2001 From: Donavan Fritz Date: Sat, 25 Apr 2026 08:26:34 -0500 Subject: [PATCH] bird: leading-edge reload + 500ms cooldown (was trailing 500ms debounce) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A single Ready/NotReady transition no longer pays a 500ms reload wait — the first call to scheduleReload fires birdc immediately; further calls within 500ms are coalesced into one tail reload at the cooldown's end. Burst behavior is the same as before: under heavy churn (deploy rolling all replicas at once), at most one reload per 500ms. Steady-state latency from pod Ready transition to crt001 BGP withdraw: - probe period (set in pod spec, 1s minimum) - ~ms informer + reconcile + birdc + BGP UPDATE The 500ms hardcoded delay is gone. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- pkg/agent/bird.go | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/pkg/agent/bird.go b/pkg/agent/bird.go index 3a38d31..c0ce126 100644 --- a/pkg/agent/bird.go +++ b/pkg/agent/bird.go @@ -24,11 +24,18 @@ type BirdManager struct { BirdctlPath string // "birdc" — overridable for tests Logger *slog.Logger - mu sync.Mutex - last string // last rendered output (de-dup) - debounce *time.Timer + mu sync.Mutex + last string // last rendered output (de-dup) + cooldown *time.Timer + cooldownEnd time.Time // window during which further reloads are coalesced + pending bool // a render landed during cooldown; reload at window end } +// reloadCooldown is the minimum spacing between two birdc reloads. The +// first change fires immediately (no leading-edge delay); follow-up +// changes within this window are coalesced into a single tail reload. +const reloadCooldown = 500 * time.Millisecond + // Render writes the config from a NodeConfig + anycast set. Idempotent — // if the rendered content matches what we last wrote, no birdc reload. func (b *BirdManager) Render(nc *flockv1alpha1.NodeConfig, anycast6, anycast4 []string, routerID string) error { @@ -88,12 +95,32 @@ func (b *BirdManager) Render(nc *flockv1alpha1.NodeConfig, anycast6, anycast4 [] return nil } -// scheduleReload coalesces birdc reload calls into ~500ms windows. +// scheduleReload uses leading-edge + cooldown semantics: the first call +// reloads immediately; subsequent calls within reloadCooldown coalesce +// into a single deferred reload at the cooldown's end. Caller holds b.mu. func (b *BirdManager) scheduleReload() { - if b.debounce != nil { - b.debounce.Stop() + now := time.Now() + if now.After(b.cooldownEnd) { + // Outside any active cooldown — fire now (leading edge). + b.cooldownEnd = now.Add(reloadCooldown) + b.pending = false + go b.reload() + return } - b.debounce = time.AfterFunc(500*time.Millisecond, b.reload) + // Inside cooldown — coalesce. If no tail timer is set, schedule one + // at the cooldown end; if already set, just leave it. + if b.pending { + return + } + b.pending = true + delay := b.cooldownEnd.Sub(now) + b.cooldown = time.AfterFunc(delay, func() { + b.mu.Lock() + b.pending = false + b.cooldownEnd = time.Now().Add(reloadCooldown) + b.mu.Unlock() + b.reload() + }) } func (b *BirdManager) reload() {