Skip to content

Commit 71290b0

Browse files
committed
chore: reimplement TypedValue by atomic.Pointer
1 parent 41b321d commit 71290b0

2 files changed

Lines changed: 19 additions & 42 deletions

File tree

common/atomic/value.go

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,7 @@ func DefaultValue[T any]() T {
1111
}
1212

1313
type TypedValue[T any] struct {
14-
_ noCopy
15-
value atomic.Value
16-
}
17-
18-
// tValue is a struct with determined type to resolve atomic.Value usages with interface types
19-
// https://github.com/golang/go/issues/22550
20-
//
21-
// The intention to have an atomic value store for errors. However, running this code panics:
22-
// panic: sync/atomic: store of inconsistently typed value into Value
23-
// This is because atomic.Value requires that the underlying concrete type be the same (which is a reasonable expectation for its implementation).
24-
// When going through the atomic.Value.Store method call, the fact that both these are of the error interface is lost.
25-
type tValue[T any] struct {
26-
value T
14+
value atomic.Pointer[T]
2715
}
2816

2917
func (t *TypedValue[T]) Load() T {
@@ -36,27 +24,36 @@ func (t *TypedValue[T]) LoadOk() (_ T, ok bool) {
3624
if value == nil {
3725
return DefaultValue[T](), false
3826
}
39-
return value.(tValue[T]).value, true
27+
return *value, true
4028
}
4129

4230
func (t *TypedValue[T]) Store(value T) {
43-
t.value.Store(tValue[T]{value})
31+
t.value.Store(&value)
4432
}
4533

4634
func (t *TypedValue[T]) Swap(new T) T {
47-
old := t.value.Swap(tValue[T]{new})
35+
old := t.value.Swap(&new)
4836
if old == nil {
4937
return DefaultValue[T]()
5038
}
51-
return old.(tValue[T]).value
39+
return *old
5240
}
5341

5442
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
55-
return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new}) ||
56-
// In the edge-case where [atomic.Value.Store] is uninitialized
57-
// and trying to compare with the zero value of T,
58-
// then compare-and-swap with the nil any value.
59-
(any(old) == any(DefaultValue[T]()) && t.value.CompareAndSwap(any(nil), tValue[T]{new}))
43+
for {
44+
currentP := t.value.Load()
45+
currentValue := DefaultValue[T]()
46+
if currentP != nil {
47+
currentValue = *currentP
48+
}
49+
// Compare old and current via runtime equality check.
50+
if any(currentValue) != any(old) {
51+
return false
52+
}
53+
if t.value.CompareAndSwap(currentP, &new) {
54+
return true
55+
}
56+
}
6057
}
6158

6259
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
@@ -89,9 +86,3 @@ func NewTypedValue[T any](t T) (v TypedValue[T]) {
8986
v.Store(t)
9087
return
9188
}
92-
93-
type noCopy struct{}
94-
95-
// Lock is a no-op used by -copylocks checker from `go vet`.
96-
func (*noCopy) Lock() {}
97-
func (*noCopy) Unlock() {}

common/atomic/value_test.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,6 @@ import (
77
)
88

99
func TestTypedValue(t *testing.T) {
10-
{
11-
// Always wrapping should not allocate for simple values
12-
// because tValue[T] has the same memory layout as T.
13-
var v TypedValue[bool]
14-
bools := []bool{true, false}
15-
if n := int(testing.AllocsPerRun(1000, func() {
16-
for _, b := range bools {
17-
v.Store(b)
18-
}
19-
})); n != 0 {
20-
t.Errorf("AllocsPerRun = %d, want 0", n)
21-
}
22-
}
23-
2410
{
2511
var v TypedValue[int]
2612
got, gotOk := v.LoadOk()

0 commit comments

Comments
 (0)