Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CompareAndSwap and Swap to String, Error, Value, Deprecate CAS #111

Merged
merged 3 commits into from Aug 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion bool.go
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion duration.go
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions error.go
Expand Up @@ -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)))
}
4 changes: 2 additions & 2 deletions 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
Expand All @@ -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 }

Expand Down
28 changes: 28 additions & 0 deletions error_test.go
Expand Up @@ -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")
}
23 changes: 15 additions & 8 deletions 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
Expand Down Expand Up @@ -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.
Expand Down
23 changes: 15 additions & 8 deletions 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
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions int32.go
Expand Up @@ -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)
}

Expand Down
7 changes: 7 additions & 0 deletions int64.go
Expand Up @@ -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)
}

Expand Down
9 changes: 8 additions & 1 deletion 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
Expand Down Expand Up @@ -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)
}

Expand Down
44 changes: 30 additions & 14 deletions 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -271,16 +270,33 @@ 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 }}

{{ if .Swap -}}
// 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 }}

Expand Down
11 changes: 11 additions & 0 deletions string.go
Expand Up @@ -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)
}
6 changes: 2 additions & 4 deletions 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
Expand All @@ -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 {
Expand Down
20 changes: 20 additions & 0 deletions string_test.go
Expand Up @@ -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")
})
}
7 changes: 7 additions & 0 deletions uint32.go
Expand Up @@ -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)
}

Expand Down