Add CompareAndSwap and Swap, Deprecate CAS (#111)
Adds CompareAndSwap and Swap methods to String, Error, and Value, implemented by making use of Value.CompareAndSwap and Value.Swap added in Go 1.17. Following that, add CompareAndSwap to all other types with "CAS" methods and deprecate CAS in favor of CompareAndSwap, since that's the convention the standard library chose for these in Go 1.19.
This commit is contained in:
9
bool.go
9
bool.go
@@ -55,8 +55,15 @@ func (x *Bool) Store(val bool) {
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap for bool values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (x *Bool) CAS(old, new bool) (swapped bool) {
|
||||
return x.v.CAS(boolToInt(old), boolToInt(new))
|
||||
return x.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for bool values.
|
||||
func (x *Bool) CompareAndSwap(old, new bool) (swapped bool) {
|
||||
return x.v.CompareAndSwap(boolToInt(old), boolToInt(new))
|
||||
}
|
||||
|
||||
// Swap atomically stores the given bool and returns the old
|
||||
|
||||
@@ -56,8 +56,15 @@ func (x *Duration) Store(val time.Duration) {
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap for time.Duration values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (x *Duration) CAS(old, new time.Duration) (swapped bool) {
|
||||
return x.v.CAS(int64(old), int64(new))
|
||||
return x.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for time.Duration values.
|
||||
func (x *Duration) CompareAndSwap(old, new time.Duration) (swapped bool) {
|
||||
return x.v.CompareAndSwap(int64(old), int64(new))
|
||||
}
|
||||
|
||||
// Swap atomically stores the given time.Duration and returns the old
|
||||
|
||||
11
error.go
11
error.go
@@ -49,3 +49,14 @@ func (x *Error) Load() error {
|
||||
func (x *Error) Store(val error) {
|
||||
x.v.Store(packError(val))
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for error values.
|
||||
func (x *Error) CompareAndSwap(old, new error) (swapped bool) {
|
||||
return x.v.CompareAndSwap(packError(old), packError(new))
|
||||
}
|
||||
|
||||
// Swap atomically stores the given error and returns the old
|
||||
// value.
|
||||
func (x *Error) Swap(val error) (old error) {
|
||||
return unpackError(x.v.Swap(packError(val)))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
// Copyright (c) 2020-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
|
||||
@@ -23,7 +23,7 @@ package atomic
|
||||
// atomic.Value panics on nil inputs, or if the underlying type changes.
|
||||
// Stabilize by always storing a custom struct that we control.
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -file=error.go
|
||||
//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -compareandswap -swap -file=error.go
|
||||
|
||||
type packedError struct{ Value error }
|
||||
|
||||
|
||||
@@ -53,3 +53,31 @@ func TestNewErrorWithError(t *testing.T) {
|
||||
atom.Store(err2)
|
||||
require.Equal(t, err2, atom.Load(), "Expected Load to return overridden value")
|
||||
}
|
||||
|
||||
func TestErrorSwap(t *testing.T) {
|
||||
err1 := errors.New("hello1")
|
||||
err2 := errors.New("hello2")
|
||||
|
||||
atom := NewError(err1)
|
||||
require.Equal(t, err1, atom.Load(), "Expected Load to return initialized value")
|
||||
|
||||
old := atom.Swap(err2)
|
||||
require.Equal(t, err2, atom.Load(), "Expected Load to return overridden value")
|
||||
require.Equal(t, err1, old, "Expected old to be initial value")
|
||||
}
|
||||
|
||||
func TestErrorCompareAndSwap(t *testing.T) {
|
||||
err1 := errors.New("hello1")
|
||||
err2 := errors.New("hello2")
|
||||
|
||||
atom := NewError(err1)
|
||||
require.Equal(t, err1, atom.Load(), "Expected Load to return initialized value")
|
||||
|
||||
swapped := atom.CompareAndSwap(err2, err2)
|
||||
require.False(t, swapped, "Expected swapped to be false")
|
||||
require.Equal(t, err1, atom.Load(), "Expected Load to return initial value")
|
||||
|
||||
swapped = atom.CompareAndSwap(err1, err2)
|
||||
require.True(t, swapped, "Expected swapped to be true")
|
||||
require.Equal(t, err2, atom.Load(), "Expected Load to return overridden value")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
// Copyright (c) 2020-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
|
||||
@@ -45,21 +45,28 @@ func (f *Float32) Sub(delta float32) float32 {
|
||||
|
||||
// CAS is an atomic compare-and-swap for float32 values.
|
||||
//
|
||||
// Note: CAS handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
|
||||
// but CAS allows a stored NaN to compare equal to a passed in NaN.
|
||||
// This avoids typical CAS loops from blocking forever, e.g.,
|
||||
// Deprecated: Use CompareAndSwap
|
||||
func (f *Float32) CAS(old, new float32) (swapped bool) {
|
||||
return f.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for float32 values.
|
||||
//
|
||||
// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
|
||||
// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN.
|
||||
// This avoids typical CompareAndSwap loops from blocking forever, e.g.,
|
||||
//
|
||||
// for {
|
||||
// old := atom.Load()
|
||||
// new = f(old)
|
||||
// if atom.CAS(old, new) {
|
||||
// if atom.CompareAndSwap(old, new) {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If CAS did not match NaN to match, then the above would loop forever.
|
||||
func (f *Float32) CAS(old, new float32) (swapped bool) {
|
||||
return f.v.CAS(math.Float32bits(old), math.Float32bits(new))
|
||||
// If CompareAndSwap did not match NaN to match, then the above would loop forever.
|
||||
func (f *Float32) CompareAndSwap(old, new float32) (swapped bool) {
|
||||
return f.v.CompareAndSwap(math.Float32bits(old), math.Float32bits(new))
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
// Copyright (c) 2020-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
|
||||
@@ -45,21 +45,28 @@ func (f *Float64) Sub(delta float64) float64 {
|
||||
|
||||
// CAS is an atomic compare-and-swap for float64 values.
|
||||
//
|
||||
// Note: CAS handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
|
||||
// but CAS allows a stored NaN to compare equal to a passed in NaN.
|
||||
// This avoids typical CAS loops from blocking forever, e.g.,
|
||||
// Deprecated: Use CompareAndSwap
|
||||
func (f *Float64) CAS(old, new float64) (swapped bool) {
|
||||
return f.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for float64 values.
|
||||
//
|
||||
// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
|
||||
// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN.
|
||||
// This avoids typical CompareAndSwap loops from blocking forever, e.g.,
|
||||
//
|
||||
// for {
|
||||
// old := atom.Load()
|
||||
// new = f(old)
|
||||
// if atom.CAS(old, new) {
|
||||
// if atom.CompareAndSwap(old, new) {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If CAS did not match NaN to match, then the above would loop forever.
|
||||
func (f *Float64) CAS(old, new float64) (swapped bool) {
|
||||
return f.v.CAS(math.Float64bits(old), math.Float64bits(new))
|
||||
// If CompareAndSwap did not match NaN to match, then the above would loop forever.
|
||||
func (f *Float64) CompareAndSwap(old, new float64) (swapped bool) {
|
||||
return f.v.CompareAndSwap(math.Float64bits(old), math.Float64bits(new))
|
||||
}
|
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
|
||||
7
int32.go
7
int32.go
@@ -66,7 +66,14 @@ func (i *Int32) Dec() int32 {
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Int32) CAS(old, new int32) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Int32) CompareAndSwap(old, new int32) (swapped bool) {
|
||||
return atomic.CompareAndSwapInt32(&i.v, old, new)
|
||||
}
|
||||
|
||||
|
||||
7
int64.go
7
int64.go
@@ -66,7 +66,14 @@ func (i *Int64) Dec() int64 {
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Int64) CAS(old, new int64) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Int64) CompareAndSwap(old, new int64) (swapped bool) {
|
||||
return atomic.CompareAndSwapInt64(&i.v, old, new)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
// Copyright (c) 2020-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
|
||||
@@ -180,7 +180,14 @@ func (i *{{ .Name }}) Dec() {{ .Wrapped }} {
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *{{ .Name }}) CAS(old, new {{ .Wrapped }}) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *{{ .Name }}) CompareAndSwap(old, new {{ .Wrapped }}) (swapped bool) {
|
||||
return atomic.CompareAndSwap{{ .Name }}(&i.v, old, new)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
// Copyright (c) 2020-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
|
||||
@@ -96,9 +96,10 @@ func run(args []string) error {
|
||||
Imports stringList
|
||||
Pack, Unpack string
|
||||
|
||||
CAS bool
|
||||
Swap bool
|
||||
JSON bool
|
||||
CAS bool
|
||||
CompareAndSwap bool
|
||||
Swap bool
|
||||
JSON bool
|
||||
|
||||
File string
|
||||
ToYear int
|
||||
@@ -129,7 +130,9 @@ func run(args []string) error {
|
||||
// Switches for individual methods. Underlying atomics must support
|
||||
// these.
|
||||
flag.BoolVar(&opts.CAS, "cas", false,
|
||||
"generate a `CAS(old, new) bool` method; requires -pack")
|
||||
"generate a deprecated `CAS(old, new) bool` method; requires -pack")
|
||||
flag.BoolVar(&opts.CompareAndSwap, "compareandswap", false,
|
||||
"generate a `CompareAndSwap(old, new) bool` method; requires -pack")
|
||||
flag.BoolVar(&opts.Swap, "swap", false,
|
||||
"generate a `Swap(new) old` method; requires -pack and -unpack")
|
||||
flag.BoolVar(&opts.JSON, "json", false,
|
||||
@@ -147,12 +150,8 @@ func run(args []string) error {
|
||||
return errors.New("either both, or neither of -pack and -unpack must be specified")
|
||||
}
|
||||
|
||||
if opts.CAS && len(opts.Pack) == 0 {
|
||||
return errors.New("flag -cas requires -pack")
|
||||
}
|
||||
|
||||
if opts.Swap && len(opts.Pack) == 0 {
|
||||
return errors.New("flag -swap requires -pack and -unpack")
|
||||
if opts.CAS {
|
||||
opts.CompareAndSwap = true
|
||||
}
|
||||
|
||||
var w io.Writer = os.Stdout
|
||||
@@ -240,7 +239,7 @@ var _zero{{ .Name }} {{ .Type }}
|
||||
|
||||
|
||||
// New{{ .Name }} creates a new {{ .Name }}.
|
||||
func New{{ .Name}}(val {{ .Type }}) *{{ .Name }} {
|
||||
func New{{ .Name }}(val {{ .Type }}) *{{ .Name }} {
|
||||
x := &{{ .Name }}{}
|
||||
if val != _zero{{ .Name }} {
|
||||
x.Store(val)
|
||||
@@ -271,8 +270,21 @@ func (x *{{ .Name }}) Store(val {{ .Type }}) {
|
||||
|
||||
{{ if .CAS -}}
|
||||
// CAS is an atomic compare-and-swap for {{ .Type }} values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (x *{{ .Name }}) CAS(old, new {{ .Type }}) (swapped bool) {
|
||||
return x.v.CAS({{ .Pack }}(old), {{ .Pack }}(new))
|
||||
return x.CompareAndSwap(old, new)
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
{{ if .CompareAndSwap -}}
|
||||
// CompareAndSwap is an atomic compare-and-swap for {{ .Type }} values.
|
||||
func (x *{{ .Name }}) CompareAndSwap(old, new {{ .Type }}) (swapped bool) {
|
||||
{{ if .Pack -}}
|
||||
return x.v.CompareAndSwap({{ .Pack }}(old), {{ .Pack }}(new))
|
||||
{{- else -}}{{- /* assume go.uber.org/atomic.Value */ -}}
|
||||
return x.v.CompareAndSwap(old, new)
|
||||
{{- end }}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
@@ -280,7 +292,11 @@ func (x *{{ .Name }}) Store(val {{ .Type }}) {
|
||||
// Swap atomically stores the given {{ .Type }} and returns the old
|
||||
// value.
|
||||
func (x *{{ .Name }}) Swap(val {{ .Type }}) (old {{ .Type }}) {
|
||||
return {{ .Unpack }}(x.v.Swap({{ .Pack }}(val)))
|
||||
{{ if .Pack -}}
|
||||
return {{ .Unpack }}(x.v.Swap({{ .Pack }}(val)))
|
||||
{{- else -}}{{- /* assume go.uber.org/atomic.Value */ -}}
|
||||
return x.v.Swap(val).({{ .Type }})
|
||||
{{- end }}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
|
||||
11
string.go
11
string.go
@@ -52,3 +52,14 @@ func (x *String) Load() string {
|
||||
func (x *String) Store(val string) {
|
||||
x.v.Store(val)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for string values.
|
||||
func (x *String) CompareAndSwap(old, new string) (swapped bool) {
|
||||
return x.v.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// Swap atomically stores the given string and returns the old
|
||||
// value.
|
||||
func (x *String) Swap(val string) (old string) {
|
||||
return x.v.Swap(val).(string)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||
// Copyright (c) 2020-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
|
||||
@@ -20,9 +20,7 @@
|
||||
|
||||
package atomic
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped=Value -file=string.go
|
||||
// Note: No Swap as String wraps Value, which wraps the stdlib sync/atomic.Value which
|
||||
// only supports Swap as of go1.17: https://github.com/golang/go/issues/39351
|
||||
//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped=Value -compareandswap -swap -file=string.go
|
||||
|
||||
// String returns the wrapped value.
|
||||
func (s *String) String() string {
|
||||
|
||||
@@ -82,4 +82,24 @@ func TestString(t *testing.T) {
|
||||
assert.Equal(t, "foo", atom.String(),
|
||||
"String() returned an unexpected value.")
|
||||
})
|
||||
|
||||
t.Run("CompareAndSwap", func(t *testing.T) {
|
||||
atom := NewString("foo")
|
||||
|
||||
swapped := atom.CompareAndSwap("bar", "bar")
|
||||
require.False(t, swapped, "swapped isn't false")
|
||||
require.Equal(t, atom.Load(), "foo", "Load returned wrong value")
|
||||
|
||||
swapped = atom.CompareAndSwap("foo", "bar")
|
||||
require.True(t, swapped, "swapped isn't true")
|
||||
require.Equal(t, atom.Load(), "bar", "Load returned wrong value")
|
||||
})
|
||||
|
||||
t.Run("Swap", func(t *testing.T) {
|
||||
atom := NewString("foo")
|
||||
|
||||
old := atom.Swap("bar")
|
||||
require.Equal(t, old, "foo", "Swap returned wrong value")
|
||||
require.Equal(t, atom.Load(), "bar", "Load returned wrong value")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,7 +66,14 @@ func (i *Uint32) Dec() uint32 {
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Uint32) CAS(old, new uint32) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Uint32) CompareAndSwap(old, new uint32) (swapped bool) {
|
||||
return atomic.CompareAndSwapUint32(&i.v, old, new)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,14 @@ func (i *Uint64) Dec() uint64 {
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Uint64) CAS(old, new uint64) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Uint64) CompareAndSwap(old, new uint64) (swapped bool) {
|
||||
return atomic.CompareAndSwapUint64(&i.v, old, new)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,14 @@ func (i *Uintptr) Dec() uintptr {
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Uintptr) CAS(old, new uintptr) (swapped bool) {
|
||||
return i.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Uintptr) CompareAndSwap(old, new uintptr) (swapped bool) {
|
||||
return atomic.CompareAndSwapUintptr(&i.v, old, new)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Uber Technologies, Inc.
|
||||
// Copyright (c) 2021-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
|
||||
@@ -53,6 +53,13 @@ func (p *UnsafePointer) Swap(val unsafe.Pointer) (old unsafe.Pointer) {
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap
|
||||
func (p *UnsafePointer) CAS(old, new unsafe.Pointer) (swapped bool) {
|
||||
return p.CompareAndSwap(old, new)
|
||||
}
|
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (p *UnsafePointer) CompareAndSwap(old, new unsafe.Pointer) (swapped bool) {
|
||||
return atomic.CompareAndSwapPointer(&p.v, old, new)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user