From 623427e72841a80b892d47690a1c425029b4d898 Mon Sep 17 00:00:00 2001 From: Ingo <71161062+iseeberg79@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:01:22 +0200 Subject: [PATCH] plugin: watchdog must assume a reset write may have just happened after restart using defer (#31635) --- plugin/watchdog.go | 6 ++++-- plugin/watchdog_test.go | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/plugin/watchdog.go b/plugin/watchdog.go index f13a8c485..3d3754e1f 100644 --- a/plugin/watchdog.go +++ b/plugin/watchdog.go @@ -79,7 +79,9 @@ type deferredState[T comparable] struct { // it is currently not possible to write this as a method func setter[T comparable](o *watchdogPlugin, set func(T) error, reset []T) func(T) error { var state *deferredState[T] - var lastUpdated time.Time + // seed with now, not zero: otherwise the first write's delay computes to 0 and skips + // deferral, which is wrong for an unknown last write + lastUpdated := o.clock.Now() var last *T // stop running wdt @@ -144,7 +146,7 @@ func setter[T comparable](o *watchdogPlugin, set func(T) error, reset []T) func( delay := max(0, o.timeout+5*time.Second-o.clock.Since(lastUpdated)) // defer update to non-reset value - if o.deferred && delay > 0 && !lastUpdated.IsZero() && !slices.Contains(reset, val) { + if o.deferred && delay > 0 && !slices.Contains(reset, val) { stopWdt() // store deferred value diff --git a/plugin/watchdog_test.go b/plugin/watchdog_test.go index 4bd95b269..abae08f3e 100644 --- a/plugin/watchdog_test.go +++ b/plugin/watchdog_test.go @@ -113,26 +113,33 @@ func TestWatchdogCancelPendingDeferredUpdate(t *testing.T) { return nil }, []int{1}) // 1 is reset value - // Value 3 (non-reset) + // Value 1 (reset) → establishes a known write time so the subsequent non-reset + // writes below are deferred based on that, not the fresh-setter startup assumption + require.NoError(t, set(1)) + require.Equal(t, []int{1}, calls) + + // Value 3 (non-reset) → deferred (no time has passed since the reset write) require.NoError(t, set(3)) - require.Equal(t, []int{3}, calls) + require.Equal(t, []int{1}, calls, "Value 3 should not be set yet") + c.Add(timeout + 5*time.Second) + require.Equal(t, []int{1, 3}, calls) // Value 2 (deferred update) require.NoError(t, set(2)) - require.Equal(t, []int{3}, calls, "Value 2 should not be set yet") + require.Equal(t, []int{1, 3}, calls, "Value 2 should not be set yet") // Wait a bit but not the full delay c.Add(30 * time.Second) // Value 1 (reset) → should cancel pending deferred update and set immediately require.NoError(t, set(1)) - require.Equal(t, []int{3, 1}, calls, "Value 1 should be set, Value 2 should be cancelled") + require.Equal(t, []int{1, 3, 1}, calls, "Value 1 should be set, Value 2 should be cancelled") // Wait for what would have been the original delay c.Add(timeout + 5*time.Second) // Value 2 should still not have been set - require.Equal(t, []int{3, 1}, calls, "Value 2 should remain cancelled") + require.Equal(t, []int{1, 3, 1}, calls, "Value 2 should remain cancelled") } func TestWatchdogDelayBackwardCompatibility(t *testing.T) {