From c72b2136f10cc70639155c4d2ebd5f6e707509ca Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Tue, 9 Aug 2022 14:18:48 -0700 Subject: [PATCH] Add Pointer[T] Adds a Pointer[T] type that provide the same functionality as Go 1.19's `sync/atomic.Pointer[T]` in both, Go 1.18 and 1.19. In Go 1.19, this uses the standard library's implementation, and in 1.18 our own custom implementation based on UnsafePointer. Refs GO-1572 Resolves #115 --- pointer_go118.go | 60 ++++++++++++++++++++++++++++++++++++++++ pointer_go119.go | 61 +++++++++++++++++++++++++++++++++++++++++ pointer_test.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 pointer_go118.go create mode 100644 pointer_go119.go create mode 100644 pointer_test.go diff --git a/pointer_go118.go b/pointer_go118.go new file mode 100644 index 0000000..e0f47db --- /dev/null +++ b/pointer_go118.go @@ -0,0 +1,60 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.18 && !go1.19 +// +build go1.18,!go1.19 + +package atomic + +import "unsafe" + +type Pointer[T any] struct { + _ nocmp // disallow non-atomic comparison + p UnsafePointer +} + +// NewPointer creates a new Pointer. +func NewPointer[T any](v *T) *Pointer[T] { + var p Pointer[T] + if v != nil { + p.p.Store(unsafe.Pointer(v)) + } + return &p +} + +// Load atomically loads the wrapped value. +func (p *Pointer[T]) Load() *T { + return (*T)(p.p.Load()) +} + +// Store atomically stores the passed value. +func (p *Pointer[T]) Store(val *T) { + p.p.Store(unsafe.Pointer(val)) +} + +// Swap atomically swaps the wrapped pointer and returns the old value. +func (p *Pointer[T]) Swap(val *T) (old *T) { + return (*T)(p.p.Swap(unsafe.Pointer(val))) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (p *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) { + return p.p.CompareAndSwap(unsafe.Pointer(old), unsafe.Pointer(new)) +} diff --git a/pointer_go119.go b/pointer_go119.go new file mode 100644 index 0000000..6726f17 --- /dev/null +++ b/pointer_go119.go @@ -0,0 +1,61 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build go1.19 +// +build go1.19 + +package atomic + +import "sync/atomic" + +// Pointer is an atomic pointer of type *T. +type Pointer[T any] struct { + _ nocmp // disallow non-atomic comparison + p atomic.Pointer[T] +} + +// NewPointer creates a new Pointer. +func NewPointer[T any](v *T) *Pointer[T] { + var p Pointer[T] + if v != nil { + p.p.Store(v) + } + return &p +} + +// Load atomically loads the wrapped value. +func (p *Pointer[T]) Load() *T { + return p.p.Load() +} + +// Store atomically stores the passed value. +func (p *Pointer[T]) Store(val *T) { + p.p.Store(val) +} + +// Swap atomically swaps the wrapped pointer and returns the old value. +func (p *Pointer[T]) Swap(val *T) (old *T) { + return p.p.Swap(val) +} + +// CompareAndSwap is an atomic compare-and-swap. +func (p *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) { + return p.p.CompareAndSwap(old, new) +} diff --git a/pointer_test.go b/pointer_test.go new file mode 100644 index 0000000..aa27d2a --- /dev/null +++ b/pointer_test.go @@ -0,0 +1,71 @@ +package atomic + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPointer(t *testing.T) { + type foo struct{ v int } + + i := foo{42} + j := foo{0} + k := foo{1} + + tests := []struct { + desc string + newAtomic func() *Pointer[foo] + initial *foo + }{ + { + desc: "New", + newAtomic: func() *Pointer[foo] { + return NewPointer(&i) + }, + initial: &i, + }, + { + desc: "New/nil", + newAtomic: func() *Pointer[foo] { + return NewPointer[foo](nil) + }, + initial: nil, + }, + { + desc: "zero value", + newAtomic: func() *Pointer[foo] { + var p Pointer[foo] + return &p + }, + initial: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + t.Run("Load", func(t *testing.T) { + atom := tt.newAtomic() + require.Equal(t, tt.initial, atom.Load(), "Load should report nil.") + }) + + t.Run("Swap", func(t *testing.T) { + atom := tt.newAtomic() + require.Equal(t, tt.initial, atom.Swap(&k), "Swap didn't return the old value.") + require.Equal(t, &k, atom.Load(), "Swap didn't set the correct value.") + }) + + t.Run("CAS", func(t *testing.T) { + atom := tt.newAtomic() + require.True(t, atom.CompareAndSwap(tt.initial, &j), "CAS didn't report a swap.") + require.Equal(t, &j, atom.Load(), "CAS didn't set the correct value.") + }) + + t.Run("Store", func(t *testing.T) { + atom := tt.newAtomic() + atom.Store(&i) + require.Equal(t, &i, atom.Load(), "Store didn't set the correct value.") + }) + }) + } +}