Skip to content

Commit

Permalink
Add Pointer[T]
Browse files Browse the repository at this point in the history
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
  • Loading branch information
abhinav committed Aug 9, 2022
1 parent adf11ee commit 4aef69d
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 0 deletions.
61 changes: 61 additions & 0 deletions pointer_go119.go
Original file line number Diff line number Diff line change
@@ -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)
}
60 changes: 60 additions & 0 deletions pointer_pre_go119.go
Original file line number Diff line number Diff line change
@@ -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.19
// +build !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))
}
71 changes: 71 additions & 0 deletions pointer_test.go
Original file line number Diff line number Diff line change
@@ -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.")
})
})
}
}

0 comments on commit 4aef69d

Please sign in to comment.