From 4d404086cd3d335bb71d4095dc7052b88bf2eb95 Mon Sep 17 00:00:00 2001 From: andig Date: Fri, 19 Jun 2026 20:36:07 +0200 Subject: [PATCH] Watchdog: don't re-assert stale value after reset A watchdog re-write tick can be in flight (blocked on the mutex) while the value is switched to the reset value. Without re-checking the context after acquiring the lock, the tick re-asserts the old value after the reset was written, leaving the device in the previous state. Skip the write when the watchdog was cancelled while waiting for the lock. --- plugin/watchdog.go | 5 +++++ plugin/watchdog_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/plugin/watchdog.go b/plugin/watchdog.go index a192d819d4d..f13a8c48570 100644 --- a/plugin/watchdog.go +++ b/plugin/watchdog.go @@ -107,6 +107,11 @@ func setter[T comparable](o *watchdogPlugin, set func(T) error, reset []T) func( o.mu.Lock() defer o.mu.Unlock() + // a reset may have cancelled us while we waited for the lock + if ctx.Err() != nil { + return nil + } + if err := set(val); err != nil { return err } diff --git a/plugin/watchdog_test.go b/plugin/watchdog_test.go index be9dc415181..4bd95b26956 100644 --- a/plugin/watchdog_test.go +++ b/plugin/watchdog_test.go @@ -165,3 +165,36 @@ func TestWatchdogDelayBackwardCompatibility(t *testing.T) { require.NoError(t, set(4)) require.Equal(t, []int{1, 3, 2, 4}, calls, "Value 4 should be set immediately (no delay)") } + +func TestWatchdogResetStopsInflightTick(t *testing.T) { + // Switching to the reset value must stop the watchdog: an in-flight tick + // must not re-assert the old value after the reset value was written. + for iter := range 200 { + p := &watchdogPlugin{ + log: util.NewLogger("test"), + timeout: 2 * time.Millisecond, + clock: clock.New(), + } + + var sawReset, stale atomic.Bool + + set := setter(p, func(v int) error { + if v == 1 { + sawReset.Store(true) + // hold the lock so an in-flight tick queues behind the reset + time.Sleep(2 * time.Millisecond) + } else if sawReset.Load() { + stale.Store(true) + } + return nil + }, []int{1}) + + require.NoError(t, set(2)) // non-reset: starts the watchdog + time.Sleep(3 * time.Millisecond) + + require.NoError(t, set(1)) // reset: must stop the watchdog + time.Sleep(5 * time.Millisecond) + + require.Falsef(t, stale.Load(), "iter %d: watchdog re-asserted old value after reset", iter) + } +}