From ecbbbe0b1ff54096b7ebde47481b3d2a42dffe39 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 11 Nov 2025 14:48:34 +0100 Subject: [PATCH] Watchdog: prevent concurrent access (#25232) --- plugin/watchdog.go | 6 ++++-- plugin/watchdog_test.go | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 plugin/watchdog_test.go diff --git a/plugin/watchdog.go b/plugin/watchdog.go index 4a2cf9872..7227af1d1 100644 --- a/plugin/watchdog.go +++ b/plugin/watchdog.go @@ -69,6 +69,7 @@ func (o *watchdogPlugin) wdt(ctx context.Context, set func() error) { func setter[T comparable](o *watchdogPlugin, set func(T) error, reset []T) func(T) error { return func(val T) error { o.mu.Lock() + defer o.mu.Unlock() // stop wdt on new write if o.cancel != nil { @@ -82,12 +83,13 @@ func setter[T comparable](o *watchdogPlugin, set func(T) error, reset []T) func( ctx, o.cancel = context.WithCancel(context.Background()) go o.wdt(ctx, func() error { + o.mu.Lock() + defer o.mu.Unlock() + return set(val) }) } - o.mu.Unlock() - return set(val) } } diff --git a/plugin/watchdog_test.go b/plugin/watchdog_test.go new file mode 100644 index 000000000..52d0274e9 --- /dev/null +++ b/plugin/watchdog_test.go @@ -0,0 +1,46 @@ +package plugin + +import ( + "errors" + "math/rand/v2" + "sync/atomic" + "testing" + "time" + + "github.com/evcc-io/evcc/util" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" +) + +func TestWatchdogSetterConcurrency(t *testing.T) { + p := &watchdogPlugin{ + log: util.NewLogger("foo"), + timeout: 10 * time.Nanosecond, + } + + var u atomic.Uint32 + + set := setter(p, func(i int) error { + if !u.CompareAndSwap(0, 1) { + return errors.New("race") + } + + time.Sleep(time.Duration(rand.Int32N(int32(p.timeout)))) + + if !u.CompareAndSwap(1, 0) { + return errors.New("race") + } + + return nil + }, nil) + + var eg errgroup.Group + + for range 100 { + eg.Go(func() error { + return set(1) + }) + } + + require.NoError(t, eg.Wait()) +}