Skip to content

Commit

Permalink
feat: add async.Value implementation (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
reugn authored Oct 28, 2023
1 parent efabd65 commit ab0c12e
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Async is a synchronization and asynchronous computation package for Go.
* **Promise** - While futures are defined as a type of read-only placeholder object created for a result which doesn’t yet exist, a promise can be thought of as a writable, single-assignment container, which completes a future.
* **Task** - A data type for controlling possibly lazy and asynchronous computations.
* **Once** - An object similar to sync.Once having the Do method taking `f func() (T, error)` and returning `(T, error)`.
* **Value** - An object similar to atomic.Value, but without the consistent type constraint.
* **WaitGroupContext** - A WaitGroup with the `context.Context` support for graceful unblocking.
* **Reentrant Lock** - Mutex that allows goroutines to enter into the lock on a resource more than once.

Expand Down
64 changes: 64 additions & 0 deletions value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package async

import (
"sync/atomic"
)

// A Value provides an atomic load and store of a value of any type.
// The behavior is analogous to atomic.Value, except that
// the value is not required to be of the same specific type.
// Can be useful for storing different implementations of an interface.
type Value struct {
p atomic.Pointer[atomic.Value]
}

// CompareAndSwap executes the compare-and-swap operation for the Value.
// The current implementation is not atomic.
func (v *Value) CompareAndSwap(old any, new any) (swapped bool) {
defer func() {
if err := recover(); err != nil {
swapped = false
}
}()
delegate := v.p.Load()
if delegate != nil {
if old == delegate.Load() {
v.p.Store(initValue(new))
return true
}
}
return false
}

// Load returns the value set by the most recent Store.
// It returns nil if there has been no call to Store for this Value.
func (v *Value) Load() (val any) {
delegate := v.p.Load()
if delegate != nil {
return delegate.Load()
}
return nil
}

// Store sets the value of the Value v to val.
// Store(nil) panics.
func (v *Value) Store(val any) {
v.p.Store(initValue(val))
}

// Swap stores new into Value and returns the previous value.
// It returns nil if the Value is empty.
// Swap(nil) panics.
func (v *Value) Swap(new any) (old any) {
oldValue := v.p.Swap(initValue(new))
if oldValue != nil {
return oldValue.Load()
}
return nil
}

func initValue(val any) *atomic.Value {
value := atomic.Value{}
value.Store(val)
return &value
}
109 changes: 109 additions & 0 deletions value_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package async

import (
"testing"

"github.com/reugn/async/internal/assert"
)

//nolint:funlen
func TestValueCompareAndSwap(t *testing.T) {
var value Value
swapped := value.CompareAndSwap(1, 2)
assert.Equal(t, swapped, false)
assert.Equal(t, value.Load(), nil)

swapped = value.CompareAndSwap(1, nil)
assert.Equal(t, swapped, false)
assert.Equal(t, value.Load(), nil)

swapped = value.CompareAndSwap(nil, 1)
assert.Equal(t, swapped, false)
assert.Equal(t, value.Load(), nil)

value.Store(1)

swapped = value.CompareAndSwap("a", nil)
assert.Equal(t, swapped, false)
assert.Equal(t, value.Load(), 1)

swapped = value.CompareAndSwap(nil, nil)
assert.Equal(t, swapped, false)
assert.Equal(t, value.Load(), 1)

swapped = value.CompareAndSwap("a", 2)
assert.Equal(t, swapped, false)
assert.Equal(t, value.Load(), 1)

swapped = value.CompareAndSwap(-1, 2)
assert.Equal(t, swapped, false)
assert.Equal(t, value.Load(), 1)

swapped = value.CompareAndSwap(1, 2)
assert.Equal(t, swapped, true)
assert.Equal(t, value.Load(), 2)

swapped = value.CompareAndSwap(2, "a")
assert.Equal(t, swapped, true)
assert.Equal(t, value.Load(), "a")

stringPointer := ptr("b")
swapped = value.CompareAndSwap("a", stringPointer)
assert.Equal(t, swapped, true)
if value.Load() != stringPointer {
t.Fail()
}

swapped = value.CompareAndSwap(ptr("b"), "c")
assert.Equal(t, swapped, false)
if value.Load() != stringPointer {
t.Fail()
}

swapped = value.CompareAndSwap(stringPointer, "c")
assert.Equal(t, swapped, true)
assert.Equal(t, value.Load(), "c")
}

func TestValueLoad(t *testing.T) {
var value Value
assert.Equal(t, value.Load(), nil)

value.Store(1)
assert.Equal(t, value.Load(), 1)
}

func TestValueStore(t *testing.T) {
var value Value
value.Store(1)
assert.Equal(t, value.Load(), 1)

assert.Panic(t, func() { value.Store(nil) })

value.Store("a")
assert.Equal(t, value.Load(), "a")

stringPointer := ptr("b")
value.Store(stringPointer)
if value.Load() != stringPointer {
t.Fail()
}
}

func TestValueSwap(t *testing.T) {
var value Value
old := value.Swap(1)
assert.Equal(t, old, nil)

assert.Panic(t, func() { _ = value.Swap(nil) })

old = value.Swap("a")
assert.Equal(t, old, 1)

stringPointer := ptr("b")
old = value.Swap(stringPointer)
assert.Equal(t, old, "a")
if value.Load() != stringPointer {
t.Fail()
}
}

0 comments on commit ab0c12e

Please sign in to comment.