From 71290b057f603f179e29147920b28f6f7526c175 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Thu, 14 Aug 2025 10:57:56 +0800 Subject: [PATCH] chore: reimplement TypedValue by atomic.Pointer --- common/atomic/value.go | 47 +++++++++++++++---------------------- common/atomic/value_test.go | 14 ----------- 2 files changed, 19 insertions(+), 42 deletions(-) diff --git a/common/atomic/value.go b/common/atomic/value.go index e63d2310..b3306939 100644 --- a/common/atomic/value.go +++ b/common/atomic/value.go @@ -11,19 +11,7 @@ func DefaultValue[T any]() T { } type TypedValue[T any] struct { - _ noCopy - value atomic.Value -} - -// tValue is a struct with determined type to resolve atomic.Value usages with interface types -// https://github.com/golang/go/issues/22550 -// -// The intention to have an atomic value store for errors. However, running this code panics: -// panic: sync/atomic: store of inconsistently typed value into Value -// This is because atomic.Value requires that the underlying concrete type be the same (which is a reasonable expectation for its implementation). -// When going through the atomic.Value.Store method call, the fact that both these are of the error interface is lost. -type tValue[T any] struct { - value T + value atomic.Pointer[T] } func (t *TypedValue[T]) Load() T { @@ -36,27 +24,36 @@ func (t *TypedValue[T]) LoadOk() (_ T, ok bool) { if value == nil { return DefaultValue[T](), false } - return value.(tValue[T]).value, true + return *value, true } func (t *TypedValue[T]) Store(value T) { - t.value.Store(tValue[T]{value}) + t.value.Store(&value) } func (t *TypedValue[T]) Swap(new T) T { - old := t.value.Swap(tValue[T]{new}) + old := t.value.Swap(&new) if old == nil { return DefaultValue[T]() } - return old.(tValue[T]).value + return *old } func (t *TypedValue[T]) CompareAndSwap(old, new T) bool { - return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new}) || - // In the edge-case where [atomic.Value.Store] is uninitialized - // and trying to compare with the zero value of T, - // then compare-and-swap with the nil any value. - (any(old) == any(DefaultValue[T]()) && t.value.CompareAndSwap(any(nil), tValue[T]{new})) + for { + currentP := t.value.Load() + currentValue := DefaultValue[T]() + if currentP != nil { + currentValue = *currentP + } + // Compare old and current via runtime equality check. + if any(currentValue) != any(old) { + return false + } + if t.value.CompareAndSwap(currentP, &new) { + return true + } + } } func (t *TypedValue[T]) MarshalJSON() ([]byte, error) { @@ -89,9 +86,3 @@ func NewTypedValue[T any](t T) (v TypedValue[T]) { v.Store(t) return } - -type noCopy struct{} - -// Lock is a no-op used by -copylocks checker from `go vet`. -func (*noCopy) Lock() {} -func (*noCopy) Unlock() {} diff --git a/common/atomic/value_test.go b/common/atomic/value_test.go index 881b3e8d..d6bc28b4 100644 --- a/common/atomic/value_test.go +++ b/common/atomic/value_test.go @@ -7,20 +7,6 @@ import ( ) func TestTypedValue(t *testing.T) { - { - // Always wrapping should not allocate for simple values - // because tValue[T] has the same memory layout as T. - var v TypedValue[bool] - bools := []bool{true, false} - if n := int(testing.AllocsPerRun(1000, func() { - for _, b := range bools { - v.Store(b) - } - })); n != 0 { - t.Errorf("AllocsPerRun = %d, want 0", n) - } - } - { var v TypedValue[int] got, gotOk := v.LoadOk()