From 00beeaa184e8dcf17ee0e9b71d9f78542b396531 Mon Sep 17 00:00:00 2001 From: Johann Sebastian Schicho Date: Thu, 2 Sep 2021 00:50:36 +0200 Subject: [PATCH] hide internal int32 data --- bool.go | 21 ++++++++++------ bool_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/bool.go b/bool.go index bf1eb86..8489bb4 100644 --- a/bool.go +++ b/bool.go @@ -24,21 +24,23 @@ func NewBool(ok bool) *AtomicBool { // AtomicBool is an atomic Boolean. // Its methods are all atomic, thus safe to be called by multiple goroutines simultaneously. // Note: When embedding into a struct one should always use *AtomicBool to avoid copy. -type AtomicBool int32 +type AtomicBool struct { + boolean int32 +} // Set sets the Boolean to true. func (ab *AtomicBool) Set() { - atomic.StoreInt32((*int32)(ab), 1) + atomic.StoreInt32((*int32)(&ab.boolean), 1) } // UnSet sets the Boolean to false. func (ab *AtomicBool) UnSet() { - atomic.StoreInt32((*int32)(ab), 0) + atomic.StoreInt32((*int32)(&ab.boolean), 0) } // IsSet returns whether the Boolean is true. func (ab *AtomicBool) IsSet() bool { - return atomic.LoadInt32((*int32)(ab)) == 1 + return atomic.LoadInt32((*int32)(&ab.boolean))&1 == 1 } // IsNotSet returns whether the Boolean is false. @@ -49,12 +51,17 @@ func (ab *AtomicBool) IsNotSet() bool { // SetTo sets the boolean with given Boolean. func (ab *AtomicBool) SetTo(yes bool) { if yes { - atomic.StoreInt32((*int32)(ab), 1) + atomic.StoreInt32((*int32)(&ab.boolean), 1) } else { - atomic.StoreInt32((*int32)(ab), 0) + atomic.StoreInt32((*int32)(&ab.boolean), 0) } } +// Toggle inverts the Boolean then returns the value before inverting. +func (ab *AtomicBool) Toggle() bool { + return atomic.AddInt32((*int32)(&ab.boolean), 1)&1 == 0 +} + // SetToIf sets the Boolean to new only if the Boolean matches the old. // Returns whether the set was done. func (ab *AtomicBool) SetToIf(old, new bool) (set bool) { @@ -65,7 +72,7 @@ func (ab *AtomicBool) SetToIf(old, new bool) (set bool) { if new { n = 1 } - return atomic.CompareAndSwapInt32((*int32)(ab), o, n) + return atomic.CompareAndSwapInt32((*int32)(&ab.boolean), o, n) } // MarshalJSON behaves the same as if the AtomicBool is a builtin.bool. diff --git a/bool_test.go b/bool_test.go index d418c48..2abd9c8 100644 --- a/bool_test.go +++ b/bool_test.go @@ -2,6 +2,7 @@ package abool import ( "encoding/json" + "math" "sync" "sync/atomic" "testing" @@ -72,6 +73,72 @@ func TestSetTo(t *testing.T) { } } +func TestToggle(t *testing.T) { + t.Parallel() + v := New() + + _ = v.Toggle() + if !v.IsSet() { + t.Fatal("AtomicBool.Toggle() to true failed") + } + + prev := v.Toggle() + if v.IsSet() == prev { + t.Fatal("AtomicBool.Toggle() to false failed") + } +} + +func TestToogleMultipleTimes(t *testing.T) { + t.Parallel() + + v := New() + pre := !v.IsSet() + for i := 0; i < 100; i++ { + v.SetTo(false) + for j := 0; j < i; j++ { + pre = v.Toggle() + } + + expected := i%2 != 0 + if v.IsSet() != expected { + t.Fatalf("AtomicBool.Toogle() doesn't work after %d calls, expected: %v, got %v", i, expected, v.IsSet()) + } + + if pre == v.IsSet() { + t.Fatalf("AtomicBool.Toogle() returned wrong value at the %dth calls, expected: %v, got %v", i, !v.IsSet(), pre) + } + } +} + +func TestToogleAfterOverflow(t *testing.T) { + t.Parallel() + + var value int32 = math.MaxInt32 + v := &AtomicBool{value} + + valueBeforeToggle := v.boolean + + // test first toggle after overflow + v.Toggle() + expected := math.MaxInt32%2 == 0 + if v.IsSet() != expected { + t.Fatalf("AtomicBool.Toogle() doesn't work after overflow, expected: %v, got %v", expected, v.IsSet()) + } + + // make sure overflow happened + var valueAfterToggle int32 = v.boolean + if valueAfterToggle >= valueBeforeToggle { + t.Fatalf("Overflow does not happen as expected, before %d, after: %d", valueBeforeToggle, valueAfterToggle) + } + + // test second toggle after overflow + v.Toggle() + expected = !expected + if v.IsSet() != expected { + t.Fatalf("AtomicBool.Toogle() doesn't work after the second call after overflow, expected: %v, got %v", expected, v.IsSet()) + } +} + func TestRace(t *testing.T) { repeat := 10000 var wg sync.WaitGroup