mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-08 07:30:01 +08:00
265 lines
4.9 KiB
Go
265 lines
4.9 KiB
Go
package syncx
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/zeromicro/go-zero/core/lang"
|
|
)
|
|
|
|
const limit = 10
|
|
|
|
func TestPoolGet(t *testing.T) {
|
|
stack := NewPool(limit, create, destroy)
|
|
ch := make(chan lang.PlaceholderType)
|
|
|
|
for i := 0; i < limit; i++ {
|
|
var fail AtomicBool
|
|
go func() {
|
|
v := stack.Get()
|
|
if v.(int) != 1 {
|
|
fail.Set(true)
|
|
}
|
|
ch <- lang.Placeholder
|
|
}()
|
|
|
|
select {
|
|
case <-ch:
|
|
case <-time.After(time.Second):
|
|
t.Fail()
|
|
}
|
|
|
|
if fail.True() {
|
|
t.Fatal("unmatch value")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPoolPopTooMany(t *testing.T) {
|
|
stack := NewPool(limit, create, destroy)
|
|
ch := make(chan lang.PlaceholderType, 1)
|
|
|
|
for i := 0; i < limit; i++ {
|
|
var wait sync.WaitGroup
|
|
wait.Add(1)
|
|
go func() {
|
|
stack.Get()
|
|
ch <- lang.Placeholder
|
|
wait.Done()
|
|
}()
|
|
|
|
wait.Wait()
|
|
select {
|
|
case <-ch:
|
|
default:
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
var waitGroup, pushWait sync.WaitGroup
|
|
waitGroup.Add(1)
|
|
pushWait.Add(1)
|
|
go func() {
|
|
pushWait.Done()
|
|
stack.Get()
|
|
waitGroup.Done()
|
|
}()
|
|
|
|
pushWait.Wait()
|
|
stack.Put(1)
|
|
waitGroup.Wait()
|
|
}
|
|
|
|
func TestPoolPopFirst(t *testing.T) {
|
|
var value int32
|
|
stack := NewPool(limit, func() any {
|
|
return atomic.AddInt32(&value, 1)
|
|
}, destroy)
|
|
|
|
for i := 0; i < 100; i++ {
|
|
v := stack.Get().(int32)
|
|
assert.Equal(t, 1, int(v))
|
|
stack.Put(v)
|
|
}
|
|
}
|
|
|
|
func TestPoolWithMaxAge(t *testing.T) {
|
|
var value int32
|
|
stack := NewPool(limit, func() any {
|
|
return atomic.AddInt32(&value, 1)
|
|
}, destroy, WithMaxAge(time.Millisecond))
|
|
|
|
v1 := stack.Get().(int32)
|
|
// put nil should not matter
|
|
stack.Put(nil)
|
|
stack.Put(v1)
|
|
time.Sleep(time.Millisecond * 10)
|
|
v2 := stack.Get().(int32)
|
|
assert.NotEqual(t, v1, v2)
|
|
}
|
|
|
|
func TestNewPoolPanics(t *testing.T) {
|
|
assert.Panics(t, func() {
|
|
NewPool(0, create, destroy)
|
|
})
|
|
}
|
|
|
|
func TestPoolDestroyAll(t *testing.T) {
|
|
var destroyed []int
|
|
var destroyCount int32
|
|
|
|
destroyFunc := func(item any) {
|
|
destroyed = append(destroyed, item.(int))
|
|
atomic.AddInt32(&destroyCount, 1)
|
|
}
|
|
|
|
pool := NewPool(limit, create, destroyFunc)
|
|
|
|
// Put some resources into the pool
|
|
pool.Put(10)
|
|
pool.Put(20)
|
|
pool.Put(30)
|
|
|
|
// Destroy all resources
|
|
pool.DestroyAll()
|
|
|
|
// Verify all resources were destroyed
|
|
assert.Equal(t, int32(3), atomic.LoadInt32(&destroyCount))
|
|
assert.Contains(t, destroyed, 10)
|
|
assert.Contains(t, destroyed, 20)
|
|
assert.Contains(t, destroyed, 30)
|
|
|
|
// Verify pool is empty - next Get should create new resource
|
|
val := pool.Get()
|
|
assert.Equal(t, 1, val) // create() returns 1
|
|
}
|
|
|
|
func TestPoolDestroyAllEmpty(t *testing.T) {
|
|
var destroyCount int32
|
|
destroyFunc := func(_ any) {
|
|
atomic.AddInt32(&destroyCount, 1)
|
|
}
|
|
|
|
pool := NewPool(limit, create, destroyFunc)
|
|
|
|
// DestroyAll on empty pool should not panic
|
|
pool.DestroyAll()
|
|
|
|
// No resources should have been destroyed
|
|
assert.Equal(t, int32(0), atomic.LoadInt32(&destroyCount))
|
|
|
|
// Pool should still work normally
|
|
val := pool.Get()
|
|
assert.Equal(t, 1, val)
|
|
}
|
|
|
|
func TestPoolDestroyAllWithNilDestroy(t *testing.T) {
|
|
pool := NewPool(limit, create, nil)
|
|
|
|
// Put some resources into the pool
|
|
pool.Put(10)
|
|
pool.Put(20)
|
|
|
|
// DestroyAll with nil destroy function should not panic
|
|
pool.DestroyAll()
|
|
|
|
// Pool should be empty and work normally
|
|
val := pool.Get()
|
|
assert.Equal(t, 1, val)
|
|
}
|
|
|
|
func TestPoolDestroyAllConcurrency(t *testing.T) {
|
|
var destroyCount int32
|
|
var createCount int32
|
|
|
|
createFunc := func() any {
|
|
return atomic.AddInt32(&createCount, 1)
|
|
}
|
|
|
|
destroyFunc := func(_ any) {
|
|
atomic.AddInt32(&destroyCount, 1)
|
|
}
|
|
|
|
pool := NewPool(limit, createFunc, destroyFunc)
|
|
|
|
// Add some initial resources
|
|
for i := 0; i < 5; i++ {
|
|
pool.Put(i + 100)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
const goroutines = 10
|
|
|
|
// Concurrently perform various operations
|
|
for i := 0; i < goroutines; i++ {
|
|
wg.Add(1)
|
|
go func(id int) {
|
|
defer wg.Done()
|
|
|
|
switch id % 4 {
|
|
case 0:
|
|
// DestroyAll
|
|
pool.DestroyAll()
|
|
case 1:
|
|
// Get resources
|
|
val := pool.Get()
|
|
pool.Put(val)
|
|
case 2:
|
|
// Put resources
|
|
pool.Put(id + 1000)
|
|
case 3:
|
|
// Get and don't put back
|
|
pool.Get()
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Final DestroyAll to clean up
|
|
pool.DestroyAll()
|
|
|
|
// Pool should work after concurrent operations
|
|
val := pool.Get()
|
|
assert.NotNil(t, val)
|
|
}
|
|
|
|
func TestPoolDestroyAllWakesWaitingGoroutines(t *testing.T) {
|
|
pool := NewPool(1, create, destroy) // Small pool size
|
|
|
|
// Fill the pool
|
|
resource := pool.Get()
|
|
assert.Equal(t, 1, resource)
|
|
|
|
var wg sync.WaitGroup
|
|
var gotResource bool
|
|
|
|
// Start a goroutine that will wait for a resource
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
val := pool.Get() // This will block since pool is full
|
|
gotResource = true
|
|
assert.Equal(t, 1, val) // Should get a newly created resource after DestroyAll
|
|
}()
|
|
|
|
// Give the goroutine time to start waiting
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
// DestroyAll should wake up the waiting goroutine
|
|
pool.DestroyAll()
|
|
|
|
wg.Wait()
|
|
assert.True(t, gotResource)
|
|
}
|
|
|
|
func create() any {
|
|
return 1
|
|
}
|
|
|
|
func destroy(_ any) {
|
|
}
|