From 5319211aad9bf58a46aca2137d95f7381f459c1e Mon Sep 17 00:00:00 2001 From: kruskall <99559985+kruskall@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:09:46 +0200 Subject: [PATCH] feat: replace custom atomic types with stdlib atomic (#70) Go 1.19 added new atomic types. Replace custom atomic implementation with native types. --- atomic/atomic.go | 94 --------------- atomic/atomic32.go | 50 -------- atomic/atomic64.go | 51 -------- atomic/atomic_test.go | 273 ------------------------------------------ refcount.go | 7 +- 5 files changed, 3 insertions(+), 472 deletions(-) delete mode 100644 atomic/atomic.go delete mode 100644 atomic/atomic32.go delete mode 100644 atomic/atomic64.go delete mode 100644 atomic/atomic_test.go diff --git a/atomic/atomic.go b/atomic/atomic.go deleted file mode 100644 index 09e8361..0000000 --- a/atomic/atomic.go +++ /dev/null @@ -1,94 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -// Package atomic provides common primitive types with atomic accessors. -package atomic - -import a "sync/atomic" - -// Bool provides an atomic boolean type. -type Bool struct{ u Uint32 } - -// Int32 provides an atomic int32 type. -type Int32 struct{ value int32 } - -// Int64 provides an atomic int64 type. -type Int64 struct{ value int64 } - -// Uint32 provides an atomic uint32 type. -type Uint32 struct{ value uint32 } - -// Uint64 provides an atomic uint64 type. -type Uint64 struct{ value uint64 } - -func MakeBool(v bool) Bool { return Bool{MakeUint32(encBool(v))} } -func NewBool(v bool) *Bool { return &Bool{MakeUint32(encBool(v))} } -func (b *Bool) Load() bool { return b.u.Load() == 1 } -func (b *Bool) Store(v bool) { b.u.Store(encBool(v)) } -func (b *Bool) Swap(new bool) bool { return b.u.Swap(encBool(new)) == 1 } -func (b *Bool) CAS(old, new bool) bool { return b.u.CAS(encBool(old), encBool(new)) } - -func MakeInt32(v int32) Int32 { return Int32{v} } -func NewInt32(v int32) *Int32 { return &Int32{v} } -func (i *Int32) Load() int32 { return a.LoadInt32(&i.value) } -func (i *Int32) Store(v int32) { a.StoreInt32(&i.value, v) } -func (i *Int32) Swap(new int32) int32 { return a.SwapInt32(&i.value, new) } -func (i *Int32) Add(delta int32) int32 { return a.AddInt32(&i.value, delta) } -func (i *Int32) Sub(delta int32) int32 { return a.AddInt32(&i.value, -delta) } -func (i *Int32) Inc() int32 { return i.Add(1) } -func (i *Int32) Dec() int32 { return i.Add(-1) } -func (i *Int32) CAS(old, new int32) bool { return a.CompareAndSwapInt32(&i.value, old, new) } - -func MakeInt64(v int64) Int64 { return Int64{v} } -func NewInt64(v int64) *Int64 { return &Int64{v} } -func (i *Int64) Load() int64 { return a.LoadInt64(&i.value) } -func (i *Int64) Store(v int64) { a.StoreInt64(&i.value, v) } -func (i *Int64) Swap(new int64) int64 { return a.SwapInt64(&i.value, new) } -func (i *Int64) Add(delta int64) int64 { return a.AddInt64(&i.value, delta) } -func (i *Int64) Sub(delta int64) int64 { return a.AddInt64(&i.value, -delta) } -func (i *Int64) Inc() int64 { return i.Add(1) } -func (i *Int64) Dec() int64 { return i.Add(-1) } -func (i *Int64) CAS(old, new int64) bool { return a.CompareAndSwapInt64(&i.value, old, new) } - -func MakeUint32(v uint32) Uint32 { return Uint32{v} } -func NewUint32(v uint32) *Uint32 { return &Uint32{v} } -func (u *Uint32) Load() uint32 { return a.LoadUint32(&u.value) } -func (u *Uint32) Store(v uint32) { a.StoreUint32(&u.value, v) } -func (u *Uint32) Swap(new uint32) uint32 { return a.SwapUint32(&u.value, new) } -func (u *Uint32) Add(delta uint32) uint32 { return a.AddUint32(&u.value, delta) } -func (u *Uint32) Sub(delta uint32) uint32 { return a.AddUint32(&u.value, ^uint32(delta-1)) } -func (u *Uint32) Inc() uint32 { return u.Add(1) } -func (u *Uint32) Dec() uint32 { return u.Add(^uint32(0)) } -func (u *Uint32) CAS(old, new uint32) bool { return a.CompareAndSwapUint32(&u.value, old, new) } - -func MakeUint64(v uint64) Uint64 { return Uint64{v} } -func NewUint64(v uint64) *Uint64 { return &Uint64{v} } -func (u *Uint64) Load() uint64 { return a.LoadUint64(&u.value) } -func (u *Uint64) Store(v uint64) { a.StoreUint64(&u.value, v) } -func (u *Uint64) Swap(new uint64) uint64 { return a.SwapUint64(&u.value, new) } -func (u *Uint64) Add(delta uint64) uint64 { return a.AddUint64(&u.value, delta) } -func (u *Uint64) Sub(delta uint64) uint64 { return a.AddUint64(&u.value, ^uint64(delta-1)) } -func (u *Uint64) Inc() uint64 { return u.Add(1) } -func (u *Uint64) Dec() uint64 { return u.Add(^uint64(0)) } -func (u *Uint64) CAS(old, new uint64) bool { return a.CompareAndSwapUint64(&u.value, old, new) } - -func encBool(b bool) uint32 { - if b { - return 1 - } - return 0 -} diff --git a/atomic/atomic32.go b/atomic/atomic32.go deleted file mode 100644 index 17c4139..0000000 --- a/atomic/atomic32.go +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -// +build 386 arm mips mipsle - -package atomic - -// atomic Uint/Int for 32bit systems - -// Uint provides an architecture specific atomic uint. -type Uint struct{ a Uint32 } - -// Int provides an architecture specific atomic uint. -type Int struct{ a Int32 } - -func MakeUint(v uint) Uint { return Uint{MakeUint32(uint32(v))} } -func NewUint(v uint) *Uint { return &Uint{MakeUint32(uint32(v))} } -func (u *Uint) Load() uint { return uint(u.a.Load()) } -func (u *Uint) Store(v uint) { u.a.Store(uint32(v)) } -func (u *Uint) Swap(new uint) uint { return uint(u.a.Swap(uint32(new))) } -func (u *Uint) Add(delta uint) uint { return uint(u.a.Add(uint32(delta))) } -func (u *Uint) Sub(delta uint) uint { return uint(u.a.Add(uint32(-delta))) } -func (u *Uint) Inc() uint { return uint(u.a.Inc()) } -func (u *Uint) Dec() uint { return uint(u.a.Dec()) } -func (u *Uint) CAS(old, new uint) bool { return u.a.CAS(uint32(old), uint32(new)) } - -func MakeInt(v int) Int { return Int{MakeInt32(int32(v))} } -func NewInt(v int) *Int { return &Int{MakeInt32(int32(v))} } -func (i *Int) Load() int { return int(i.a.Load()) } -func (i *Int) Store(v int) { i.a.Store(int32(v)) } -func (i *Int) Swap(new int) int { return int(i.a.Swap(int32(new))) } -func (i *Int) Add(delta int) int { return int(i.a.Add(int32(delta))) } -func (i *Int) Sub(delta int) int { return int(i.a.Add(int32(-delta))) } -func (i *Int) Inc() int { return int(i.a.Inc()) } -func (i *Int) Dec() int { return int(i.a.Dec()) } -func (i *Int) CAS(old, new int) bool { return i.a.CAS(int32(old), int32(new)) } diff --git a/atomic/atomic64.go b/atomic/atomic64.go deleted file mode 100644 index e0fc1ee..0000000 --- a/atomic/atomic64.go +++ /dev/null @@ -1,51 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -//go:build amd64 || arm64 || loong64 || ppc64 || ppc64le || mips64 || mips64le || s390x -// +build amd64 arm64 loong64 ppc64 ppc64le mips64 mips64le s390x - -package atomic - -// atomic Uint/Int for 64bit systems - -// Uint provides an architecture specific atomic uint. -type Uint struct{ a Uint64 } - -// Int provides an architecture specific atomic uint. -type Int struct{ a Int64 } - -func MakeUint(v uint) Uint { return Uint{MakeUint64(uint64(v))} } -func NewUint(v uint) *Uint { return &Uint{MakeUint64(uint64(v))} } -func (u *Uint) Load() uint { return uint(u.a.Load()) } -func (u *Uint) Store(v uint) { u.a.Store(uint64(v)) } -func (u *Uint) Swap(new uint) uint { return uint(u.a.Swap(uint64(new))) } -func (u *Uint) Add(delta uint) uint { return uint(u.a.Add(uint64(delta))) } -func (u *Uint) Sub(delta uint) uint { return uint(u.a.Add(uint64(-delta))) } -func (u *Uint) Inc() uint { return uint(u.a.Inc()) } -func (u *Uint) Dec() uint { return uint(u.a.Dec()) } -func (u *Uint) CAS(old, new uint) bool { return u.a.CAS(uint64(old), uint64(new)) } - -func MakeInt(v int) Int { return Int{MakeInt64(int64(v))} } -func NewInt(v int) *Int { return &Int{MakeInt64(int64(v))} } -func (i *Int) Load() int { return int(i.a.Load()) } -func (i *Int) Store(v int) { i.a.Store(int64(v)) } -func (i *Int) Swap(new int) int { return int(i.a.Swap(int64(new))) } -func (i *Int) Add(delta int) int { return int(i.a.Add(int64(delta))) } -func (i *Int) Sub(delta int) int { return int(i.a.Add(int64(-delta))) } -func (i *Int) Inc() int { return int(i.a.Inc()) } -func (i *Int) Dec() int { return int(i.a.Dec()) } -func (i *Int) CAS(old, new int) bool { return i.a.CAS(int64(old), int64(new)) } diff --git a/atomic/atomic_test.go b/atomic/atomic_test.go deleted file mode 100644 index f4df6fc..0000000 --- a/atomic/atomic_test.go +++ /dev/null @@ -1,273 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -package atomic - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAtomicBool(t *testing.T) { - assert := assert.New(t) - - var b Bool - assert.False(b.Load(), "check zero value is false") - - b = MakeBool(true) - assert.True(b.Load(), "check value initializer with 'true' value") - - b.Store(false) - assert.False(b.Load(), "check store to false") - - old := b.Swap(true) - assert.False(old, "check old value of swap operation is 'false'") - assert.True(b.Load(), "check new value after swap is 'true'") - - old = b.Swap(false) - assert.True(old, "check old value of second swap operation is 'true'") - assert.False(b.Load(), "check new value after second swap is 'false'") - - ok := b.CAS(true, true) - assert.False(ok, "check CAS fails with wrong 'old' value") - assert.False(b.Load(), "check failed CAS did not change value 'false'") - - ok = b.CAS(false, true) - assert.True(ok, "check CAS succeeds with correct 'old' value") - assert.True(b.Load(), "check CAS did change value to 'true'") -} - -func TestAtomicInt32(t *testing.T) { - assert := assert.New(t) - check := func(expected, actual int32, msg string) { - assert.Equal(expected, actual, msg) - } - - var v Int32 - check(0, v.Load(), "check zero value") - - v = MakeInt32(23) - check(23, v.Load(), "check value initializer") - - v.Store(42) - check(42, v.Load(), "check store new value") - - new := v.Inc() - check(43, new, "check increment returns new value") - check(43, v.Load(), "check increment did store new value") - - new = v.Dec() - check(42, new, "check decrement returns new value") - check(42, v.Load(), "check decrement did store new value") - - new = v.Add(8) - check(50, new, "check add returns new value") - check(50, v.Load(), "check add did store new value") - - new = v.Sub(8) - check(42, new, "check sub returns new value") - check(42, v.Load(), "check sub did store new value") - - old := v.Swap(101) - check(42, old, "check swap returns old value") - check(101, v.Load(), "check swap stores new value") - - ok := v.CAS(0, 23) - assert.False(ok, "check CAS with wrong old value fails") - check(101, v.Load(), "check failed CAS did not change value") - - ok = v.CAS(101, 23) - assert.True(ok, "check CAS succeeds") - check(23, v.Load(), "check CAS did store new value") -} - -func TestAtomicInt64(t *testing.T) { - assert := assert.New(t) - check := func(expected, actual int64, msg string) { - assert.Equal(expected, actual, msg) - } - - var v Int64 - check(0, v.Load(), "check zero value") - - v = MakeInt64(23) - check(23, v.Load(), "check value initializer") - - v.Store(42) - check(42, v.Load(), "check store new value") - - new := v.Inc() - check(43, new, "check increment returns new value") - check(43, v.Load(), "check increment did store new value") - - new = v.Dec() - check(42, new, "check decrement returns new value") - check(42, v.Load(), "check decrement did store new value") - - new = v.Add(8) - check(50, new, "check add returns new value") - check(50, v.Load(), "check add did store new value") - - new = v.Sub(8) - check(42, new, "check sub returns new value") - check(42, v.Load(), "check sub did store new value") - - old := v.Swap(101) - check(42, old, "check swap returns old value") - check(101, v.Load(), "check swap stores new value") - - ok := v.CAS(0, 23) - assert.False(ok, "check CAS with wrong old value fails") - check(101, v.Load(), "check failed CAS did not change value") - - ok = v.CAS(101, 23) - assert.True(ok, "check CAS succeeds") - check(23, v.Load(), "check CAS did store new value") -} - -func TestAtomicUint32(t *testing.T) { - assert := assert.New(t) - check := func(expected, actual uint32, msg string) { - assert.Equal(expected, actual, msg) - } - - var v Uint32 - check(0, v.Load(), "check zero value") - - v = MakeUint32(23) - check(23, v.Load(), "check value initializer") - - v.Store(42) - check(42, v.Load(), "check store new value") - - new := v.Inc() - check(43, new, "check increment returns new value") - check(43, v.Load(), "check increment did store new value") - - new = v.Dec() - check(42, new, "check decrement returns new value") - check(42, v.Load(), "check decrement did store new value") - - new = v.Add(8) - check(50, new, "check add returns new value") - check(50, v.Load(), "check add did store new value") - - new = v.Sub(8) - check(42, new, "check sub returns new value") - check(42, v.Load(), "check sub did store new value") - - old := v.Swap(101) - check(42, old, "check swap returns old value") - check(101, v.Load(), "check swap stores new value") - - ok := v.CAS(0, 23) - assert.False(ok, "check CAS with wrong old value fails") - check(101, v.Load(), "check failed CAS did not change value") - - ok = v.CAS(101, 23) - assert.True(ok, "check CAS succeeds") - check(23, v.Load(), "check CAS did store new value") -} - -func TestAtomicUint64(t *testing.T) { - assert := assert.New(t) - check := func(expected, actual uint64, msg string) { - assert.Equal(expected, actual, msg) - } - - var v Uint64 - check(0, v.Load(), "check zero value") - - v = MakeUint64(23) - check(23, v.Load(), "check value initializer") - - v.Store(42) - check(42, v.Load(), "check store new value") - - new := v.Inc() - check(43, new, "check increment returns new value") - check(43, v.Load(), "check increment did store new value") - - new = v.Dec() - check(42, new, "check decrement returns new value") - check(42, v.Load(), "check decrement did store new value") - - new = v.Add(8) - check(50, new, "check add returns new value") - check(50, v.Load(), "check add did store new value") - - new = v.Sub(8) - check(42, new, "check sub returns new value") - check(42, v.Load(), "check sub did store new value") - - old := v.Swap(101) - check(42, old, "check swap returns old value") - check(101, v.Load(), "check swap stores new value") - - ok := v.CAS(0, 23) - assert.False(ok, "check CAS with wrong old value fails") - check(101, v.Load(), "check failed CAS did not change value") - - ok = v.CAS(101, 23) - assert.True(ok, "check CAS succeeds") - check(23, v.Load(), "check CAS did store new value") -} - -func TestAtomicUint(t *testing.T) { - assert := assert.New(t) - check := func(expected, actual uint, msg string) { - assert.Equal(expected, actual, msg) - } - - var v Uint - check(0, v.Load(), "check zero value") - - v = MakeUint(23) - check(23, v.Load(), "check value initializer") - - v.Store(42) - check(42, v.Load(), "check store new value") - - new := v.Inc() - check(43, new, "check increment returns new value") - check(43, v.Load(), "check increment did store new value") - - new = v.Dec() - check(42, new, "check decrement returns new value") - check(42, v.Load(), "check decrement did store new value") - - new = v.Add(8) - check(50, new, "check add returns new value") - check(50, v.Load(), "check add did store new value") - - new = v.Sub(8) - check(42, new, "check sub returns new value") - check(42, v.Load(), "check sub did store new value") - - old := v.Swap(101) - check(42, old, "check swap returns old value") - check(101, v.Load(), "check swap stores new value") - - ok := v.CAS(0, 23) - assert.False(ok, "check CAS with wrong old value fails") - check(101, v.Load(), "check failed CAS did not change value") - - ok = v.CAS(101, 23) - assert.True(ok, "check CAS succeeds") - check(23, v.Load(), "check CAS did store new value") -} diff --git a/refcount.go b/refcount.go index 3574e35..936e67b 100644 --- a/refcount.go +++ b/refcount.go @@ -19,8 +19,7 @@ package concert import ( "sync" - - "github.com/elastic/go-concert/atomic" + "sync/atomic" ) // RefCount is an atomic reference counter. It can be used to track a shared @@ -47,7 +46,7 @@ const refCountOops uint32 = refCountFree - 1 // Retain increases the ref count. func (c *RefCount) Retain() { - if c.count.Inc() == 0 { + if c.count.Add(1) == 0 { panic("retaining released ref count") } } @@ -58,7 +57,7 @@ func (c *RefCount) Retain() { // If an Action is configured, then this action will be run once the // refcount becomes free. func (c *RefCount) Release() bool { - switch c.count.Dec() { + switch c.count.Add(^uint32(0)) { case refCountFree: if c.Action != nil { c.Action(c.err)