diff --git a/bool.go b/bool.go index caa373e..dfa2085 100644 --- a/bool.go +++ b/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 diff --git a/duration.go b/duration.go index 113f8fb..6f41574 100644 --- a/duration.go +++ b/duration.go @@ -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 diff --git a/error.go b/error.go index 2af1ef6..27b23ea 100644 --- a/error.go +++ b/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))) +} diff --git a/error_ext.go b/error_ext.go index ffe0be2..d31fb63 100644 --- a/error_ext.go +++ b/error_ext.go @@ -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 } diff --git a/error_test.go b/error_test.go index cab2e7d..c9ac2ef 100644 --- a/error_test.go +++ b/error_test.go @@ -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") +} diff --git a/float32_ext.go b/float32_ext.go index 39034d2..b0cd8d9 100644 --- a/float32_ext.go +++ b/float32_ext.go @@ -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. diff --git a/float64_ext.go b/float64_ext.go index 0bcaaed..48c52b0 100644 --- a/float64_ext.go +++ b/float64_ext.go @@ -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. diff --git a/int32.go b/int32.go index b513810..b9a68f4 100644 --- a/int32.go +++ b/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) } diff --git a/int64.go b/int64.go index e11a568..78d2609 100644 --- a/int64.go +++ b/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) } diff --git a/internal/gen-atomicint/main.go b/internal/gen-atomicint/main.go index b00a208..6132691 100644 --- a/internal/gen-atomicint/main.go +++ b/internal/gen-atomicint/main.go @@ -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) } diff --git a/internal/gen-atomicwrapper/main.go b/internal/gen-atomicwrapper/main.go index 1dd056d..b85501c 100644 --- a/internal/gen-atomicwrapper/main.go +++ b/internal/gen-atomicwrapper/main.go @@ -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 }} diff --git a/string.go b/string.go index 46a89dc..c4bea70 100644 --- a/string.go +++ b/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) +} diff --git a/string_ext.go b/string_ext.go index 83d92ed..1f63dfd 100644 --- a/string_ext.go +++ b/string_ext.go @@ -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 { diff --git a/string_test.go b/string_test.go index a730f5f..54121d1 100644 --- a/string_test.go +++ b/string_test.go @@ -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") + }) } diff --git a/uint32.go b/uint32.go index 60e832d..d6f04a9 100644 --- a/uint32.go +++ b/uint32.go @@ -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) } diff --git a/uint64.go b/uint64.go index 115b3db..2574bdd 100644 --- a/uint64.go +++ b/uint64.go @@ -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) } diff --git a/uintptr.go b/uintptr.go index e4a361c..81b275a 100644 --- a/uintptr.go +++ b/uintptr.go @@ -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) } diff --git a/unsafe_pointer.go b/unsafe_pointer.go index 169f793..34868ba 100644 --- a/unsafe_pointer.go +++ b/unsafe_pointer.go @@ -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) }