From 05e06dc209fa60b017736c597cc1b3dc61db04ab Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Jun 2021 08:44:58 +0800 Subject: [PATCH] Add uintptr, unsafe.Pointer atomics (#90) Add support for atomic `uintptr` and `unsafe.Pointer` types. For `unsafe.Pointer`, name the atomic variant `atomic.UnsafePointer` to maintain the "unsafe" portion of the name in usage. Resolves #88 --- CHANGELOG.md | 5 ++ gen.go | 1 + uintptr.go | 102 +++++++++++++++++++++++++++++++++++++++++ uintptr_test.go | 76 ++++++++++++++++++++++++++++++ unsafe_pointer.go | 58 +++++++++++++++++++++++ unsafe_pointer_test.go | 83 +++++++++++++++++++++++++++++++++ 6 files changed, 325 insertions(+) create mode 100644 uintptr.go create mode 100644 uintptr_test.go create mode 100644 unsafe_pointer.go create mode 100644 unsafe_pointer_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 24c0274..a010ad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +### Added +- Add `atomic.Uintptr` type for atomic operations on `uintptr` values. +- Add `atomic.UnsafePointer` type for atomic operations on `unsafe.Pointer` values. + ## [1.7.0] - 2020-09-14 ### Added - Support JSON serialization and deserialization of primitive atomic types. diff --git a/gen.go b/gen.go index 50d6b24..1e9ef4f 100644 --- a/gen.go +++ b/gen.go @@ -24,3 +24,4 @@ package atomic //go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go //go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go //go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go +//go:generate bin/gen-atomicint -name=Uintptr -wrapped=uintptr -unsigned -file=uintptr.go diff --git a/uintptr.go b/uintptr.go new file mode 100644 index 0000000..6b363ad --- /dev/null +++ b/uintptr.go @@ -0,0 +1,102 @@ +// @generated Code generated by gen-atomicint. + +// Copyright (c) 2020-2021 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. + +package atomic + +import ( + "encoding/json" + "strconv" + "sync/atomic" +) + +// Uintptr is an atomic wrapper around uintptr. +type Uintptr struct { + _ nocmp // disallow non-atomic comparison + + v uintptr +} + +// NewUintptr creates a new Uintptr. +func NewUintptr(i uintptr) *Uintptr { + return &Uintptr{v: i} +} + +// Load atomically loads the wrapped value. +func (i *Uintptr) Load() uintptr { + return atomic.LoadUintptr(&i.v) +} + +// Add atomically adds to the wrapped uintptr and returns the new value. +func (i *Uintptr) Add(n uintptr) uintptr { + return atomic.AddUintptr(&i.v, n) +} + +// Sub atomically subtracts from the wrapped uintptr and returns the new value. +func (i *Uintptr) Sub(n uintptr) uintptr { + return atomic.AddUintptr(&i.v, ^(n - 1)) +} + +// Inc atomically increments the wrapped uintptr and returns the new value. +func (i *Uintptr) Inc() uintptr { + return i.Add(1) +} + +// Dec atomically decrements the wrapped uintptr and returns the new value. +func (i *Uintptr) Dec() uintptr { + return i.Sub(1) +} + +// CAS is an atomic compare-and-swap. +func (i *Uintptr) CAS(old, new uintptr) bool { + return atomic.CompareAndSwapUintptr(&i.v, old, new) +} + +// Store atomically stores the passed value. +func (i *Uintptr) Store(n uintptr) { + atomic.StoreUintptr(&i.v, n) +} + +// Swap atomically swaps the wrapped uintptr and returns the old value. +func (i *Uintptr) Swap(n uintptr) uintptr { + return atomic.SwapUintptr(&i.v, n) +} + +// MarshalJSON encodes the wrapped uintptr into JSON. +func (i *Uintptr) MarshalJSON() ([]byte, error) { + return json.Marshal(i.Load()) +} + +// UnmarshalJSON decodes JSON into the wrapped uintptr. +func (i *Uintptr) UnmarshalJSON(b []byte) error { + var v uintptr + if err := json.Unmarshal(b, &v); err != nil { + return err + } + i.Store(v) + return nil +} + +// String encodes the wrapped value as a string. +func (i *Uintptr) String() string { + v := i.Load() + return strconv.FormatUint(uint64(v), 10) +} diff --git a/uintptr_test.go b/uintptr_test.go new file mode 100644 index 0000000..9261b08 --- /dev/null +++ b/uintptr_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2021 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. + +package atomic + +import ( + "encoding/json" + "math" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUintptr(t *testing.T) { + atom := NewUintptr(42) + + require.Equal(t, uintptr(42), atom.Load(), "Load didn't work.") + require.Equal(t, uintptr(46), atom.Add(4), "Add didn't work.") + require.Equal(t, uintptr(44), atom.Sub(2), "Sub didn't work.") + require.Equal(t, uintptr(45), atom.Inc(), "Inc didn't work.") + require.Equal(t, uintptr(44), atom.Dec(), "Dec didn't work.") + + require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.") + require.Equal(t, uintptr(0), atom.Load(), "CAS didn't set the correct value.") + + require.Equal(t, uintptr(0), atom.Swap(1), "Swap didn't return the old value.") + require.Equal(t, uintptr(1), atom.Load(), "Swap didn't set the correct value.") + + atom.Store(42) + require.Equal(t, uintptr(42), atom.Load(), "Store didn't set the correct value.") + + t.Run("JSON/Marshal", func(t *testing.T) { + bytes, err := json.Marshal(atom) + require.NoError(t, err, "json.Marshal errored unexpectedly.") + require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.") + }) + + t.Run("JSON/Unmarshal", func(t *testing.T) { + err := json.Unmarshal([]byte("40"), &atom) + require.NoError(t, err, "json.Unmarshal errored unexpectedly.") + require.Equal(t, uintptr(40), atom.Load(), "json.Unmarshal didn't set the correct value.") + }) + + t.Run("JSON/Unmarshal/Error", func(t *testing.T) { + err := json.Unmarshal([]byte(`"40"`), &atom) + require.Error(t, err, "json.Unmarshal didn't error as expected.") + assertErrorJSONUnmarshalType(t, err, + "json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) + }) + + t.Run("String", func(t *testing.T) { + // Use an integer with the signed bit set. If we're converting + // incorrectly, we'll get a negative value here. + atom := NewUintptr(uintptr(math.MaxUint64)) + assert.Equal(t, "18446744073709551615", atom.String(), + "String() returned an unexpected value.") + }) +} diff --git a/unsafe_pointer.go b/unsafe_pointer.go new file mode 100644 index 0000000..a3830c6 --- /dev/null +++ b/unsafe_pointer.go @@ -0,0 +1,58 @@ +// Copyright (c) 2021 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. + +package atomic + +import ( + "sync/atomic" + "unsafe" +) + +// UnsafePointer is an atomic wrapper around unsafe.Pointer. +type UnsafePointer struct { + _ nocmp // disallow non-atomic comparison + + v unsafe.Pointer +} + +// NewUnsafePointer creates a new UnsafePointer. +func NewUnsafePointer(p unsafe.Pointer) *UnsafePointer { + return &UnsafePointer{v: p} +} + +// Load atomically loads the wrapped value. +func (p *UnsafePointer) Load() unsafe.Pointer { + return atomic.LoadPointer(&p.v) +} + +// Store atomically stores the passed value. +func (p *UnsafePointer) Store(q unsafe.Pointer) { + atomic.StorePointer(&p.v, q) +} + +// Swap atomically swaps the wrapped unsafe.Pointer and returns the old value. +func (p *UnsafePointer) Swap(q unsafe.Pointer) unsafe.Pointer { + return atomic.SwapPointer(&p.v, q) +} + +// CAS is an atomic compare-and-swap. +func (p *UnsafePointer) CAS(old, new unsafe.Pointer) bool { + return atomic.CompareAndSwapPointer(&p.v, old, new) +} diff --git a/unsafe_pointer_test.go b/unsafe_pointer_test.go new file mode 100644 index 0000000..f0193df --- /dev/null +++ b/unsafe_pointer_test.go @@ -0,0 +1,83 @@ +// Copyright (c) 2021 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. + +package atomic + +import ( + "testing" + "unsafe" + + "github.com/stretchr/testify/require" +) + +func TestUnsafePointer(t *testing.T) { + i := int64(42) + j := int64(0) + k := int64(1) + + tests := []struct { + desc string + newAtomic func() *UnsafePointer + initial unsafe.Pointer + }{ + { + desc: "non-empty", + newAtomic: func() *UnsafePointer { + return NewUnsafePointer(unsafe.Pointer(&i)) + }, + initial: unsafe.Pointer(&i), + }, + { + desc: "nil", + newAtomic: func() *UnsafePointer { + var p UnsafePointer + return &p + }, + initial: unsafe.Pointer(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(unsafe.Pointer(&k)), "Swap didn't return the old value.") + require.Equal(t, unsafe.Pointer(&k), atom.Load(), "Swap didn't set the correct value.") + }) + + t.Run("CAS", func(t *testing.T) { + atom := tt.newAtomic() + require.True(t, atom.CAS(tt.initial, unsafe.Pointer(&j)), "CAS didn't report a swap.") + require.Equal(t, unsafe.Pointer(&j), atom.Load(), "CAS didn't set the correct value.") + }) + + t.Run("Store", func(t *testing.T) { + atom := tt.newAtomic() + atom.Store(unsafe.Pointer(&i)) + require.Equal(t, unsafe.Pointer(&i), atom.Load(), "Store didn't set the correct value.") + }) + }) + } +}