refactor: remove NoCopy struct; move RefCounter struct to goutils and update usage; remove internal/utils entirely

This commit is contained in:
yusing
2026-01-07 17:17:12 +08:00
parent 04dd963535
commit cc406921cb
8 changed files with 4 additions and 152 deletions

View File

@@ -1,8 +0,0 @@
package utils
// empty struct that implements Locker interface
// for hinting that no copy should be performed.
type NoCopy struct{}
func (*NoCopy) Lock() {}
func (*NoCopy) Unlock() {}

View File

@@ -1,54 +0,0 @@
package utils
import (
"sync/atomic"
)
type RefCount struct {
_ NoCopy
refCount uint32
zeroCh chan struct{}
}
func NewRefCounter() *RefCount {
rc := &RefCount{
refCount: 1,
zeroCh: make(chan struct{}),
}
return rc
}
func (rc *RefCount) Zero() <-chan struct{} {
return rc.zeroCh
}
func (rc *RefCount) Add() {
// We add before checking to ensure proper ordering
newV := atomic.AddUint32(&rc.refCount, 1)
if newV == 1 {
// If it was 0 before we added, that means we're incrementing after a close
// This is a programming error
panic("RefCount.Add() called after count reached zero")
}
}
func (rc *RefCount) Sub() {
// First read the current value
for {
current := atomic.LoadUint32(&rc.refCount)
if current == 0 {
// Already at zero, channel should be closed
return
}
// Try to decrement, but only if the value hasn't changed
if atomic.CompareAndSwapUint32(&rc.refCount, current, current-1) {
if current == 1 { // Was this the last reference?
close(rc.zeroCh)
}
return
}
// If CAS failed, someone else modified the count, try again
}
}

View File

@@ -1,78 +0,0 @@
package utils
import (
"sync"
"testing"
"time"
expect "github.com/yusing/goutils/testing"
)
func TestRefCounterAddSub(t *testing.T) {
rc := NewRefCounter() // Count starts at 1
var wg sync.WaitGroup
rc.Add()
for range 2 {
wg.Go(rc.Sub)
}
wg.Wait()
expect.Equal(t, int(rc.refCount), 0)
select {
case <-rc.Zero():
// Expected behavior
case <-time.After(1 * time.Second):
t.Fatal("Expected Zero channel to close, but it didn't")
}
}
func TestRefCounterMultipleAddSub(t *testing.T) {
rc := NewRefCounter()
var wg sync.WaitGroup
numAdds := 5
numSubs := 5
wg.Add(numAdds)
for range numAdds {
go func() {
defer wg.Done()
rc.Add()
}()
}
wg.Wait()
expect.Equal(t, int(rc.refCount), numAdds+1)
wg.Add(numSubs)
for range numSubs {
go func() {
defer wg.Done()
rc.Sub()
}()
}
wg.Wait()
expect.Equal(t, int(rc.refCount), numAdds+1-numSubs)
rc.Sub()
select {
case <-rc.Zero():
// Expected behavior
case <-time.After(1 * time.Second):
t.Fatal("Expected Zero channel to close, but it didn't")
}
}
func TestRefCounterOneInitially(t *testing.T) {
rc := NewRefCounter()
rc.Sub() // Bring count to zero
select {
case <-rc.Zero():
// Expected behavior
case <-time.After(1 * time.Second):
t.Fatal("Expected Zero channel to close, but it didn't")
}
}