diff --git a/bool.go b/bool.go index 8489bb4..6e8c509 100644 --- a/bool.go +++ b/bool.go @@ -58,8 +58,15 @@ func (ab *AtomicBool) SetTo(yes bool) { } // Toggle inverts the Boolean then returns the value before inverting. +// Based on: https://github.com/uber-go/atomic/blob/3504dfaa1fa414923b1c8693f45d2f6931daf229/bool_ext.go#L40 func (ab *AtomicBool) Toggle() bool { - return atomic.AddInt32((*int32)(&ab.boolean), 1)&1 == 0 + var old bool + for { + old = ab.IsSet() + if ab.SetToIf(old, !old) { + return old + } + } } // SetToIf sets the Boolean to new only if the Boolean matches the old. diff --git a/bool_test.go b/bool_test.go index 2abd9c8..2a977dd 100644 --- a/bool_test.go +++ b/bool_test.go @@ -2,7 +2,6 @@ package abool import ( "encoding/json" - "math" "sync" "sync/atomic" "testing" @@ -110,35 +109,6 @@ func TestToogleMultipleTimes(t *testing.T) { } } -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 @@ -171,6 +141,22 @@ func TestRace(t *testing.T) { wg.Wait() } +func TestSetToIfAfterMultipleToggles(t *testing.T) { + v := New() // false + + v.Toggle() // true + v.Toggle() // false + v.Toggle() // true + + // As v is true, it should now be flipped to false + v.SetToIf(true, false) + expected := false + + if v.IsSet() != expected { + t.Fatalf("Toggling the value atleast 3 times, until it's true, `SetToIf(true, false)` should flip v to false, expected: %v, got %v", expected, v.IsSet()) + } +} + func TestJSONCompatibleWithBuiltinBool(t *testing.T) { for _, value := range []bool{true, false} { // Test bool -> bytes -> AtomicBool @@ -248,6 +234,14 @@ func ExampleAtomicBool() { cond.SetToIf(new, old) // Sets to `new` only if the Boolean matches the `old`, returns whether succeeded } +func BenchmarkAtomicBoolToggle(b *testing.B) { + v := New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = v.Toggle() + } +} + // Benchmark Read func BenchmarkMutexRead(b *testing.B) {