vitess-gh/go/pools/resource_pool_test.go

492 строки
11 KiB
Go

// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pools
import (
"errors"
"testing"
"time"
"github.com/youtube/vitess/go/sync2"
"golang.org/x/net/context"
)
var lastID, count sync2.AtomicInt64
type TestResource struct {
num int64
closed bool
}
func (tr *TestResource) Close() {
if !tr.closed {
count.Add(-1)
tr.closed = true
}
}
func PoolFactory() (Resource, error) {
count.Add(1)
return &TestResource{lastID.Add(1), false}, nil
}
func FailFactory() (Resource, error) {
return nil, errors.New("Failed")
}
func SlowFailFactory() (Resource, error) {
time.Sleep(10 * time.Nanosecond)
return nil, errors.New("Failed")
}
func TestOpen(t *testing.T) {
ctx := context.Background()
lastID.Set(0)
count.Set(0)
p := NewResourcePool(PoolFactory, 6, 6, time.Second)
p.SetCapacity(5)
var resources [10]Resource
// Test Get
for i := 0; i < 5; i++ {
r, err := p.Get(ctx)
resources[i] = r
if err != nil {
t.Errorf("Unexpected error %v", err)
}
_, available, _, waitCount, waitTime, _ := p.Stats()
if available != int64(5-i-1) {
t.Errorf("expecting %d, received %d", 5-i-1, available)
}
if waitCount != 0 {
t.Errorf("expecting 0, received %d", waitCount)
}
if waitTime != 0 {
t.Errorf("expecting 0, received %d", waitTime)
}
if lastID.Get() != int64(i+1) {
t.Errorf("Expecting %d, received %d", i+1, lastID.Get())
}
if count.Get() != int64(i+1) {
t.Errorf("Expecting %d, received %d", i+1, count.Get())
}
}
// Test that Get waits
ch := make(chan bool)
go func() {
for i := 0; i < 5; i++ {
r, err := p.Get(ctx)
if err != nil {
t.Errorf("Get failed: %v", err)
}
resources[i] = r
}
for i := 0; i < 5; i++ {
p.Put(resources[i])
}
ch <- true
}()
for i := 0; i < 5; i++ {
// Sleep to ensure the goroutine waits
time.Sleep(10 * time.Millisecond)
p.Put(resources[i])
}
<-ch
_, _, _, waitCount, waitTime, _ := p.Stats()
if waitCount != 5 {
t.Errorf("Expecting 5, received %d", waitCount)
}
if waitTime == 0 {
t.Errorf("Expecting non-zero")
}
if lastID.Get() != 5 {
t.Errorf("Expecting 5, received %d", lastID.Get())
}
// Test Close resource
r, err := p.Get(ctx)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
r.Close()
p.Put(nil)
if count.Get() != 4 {
t.Errorf("Expecting 4, received %d", count.Get())
}
for i := 0; i < 5; i++ {
r, err := p.Get(ctx)
if err != nil {
t.Errorf("Get failed: %v", err)
}
resources[i] = r
}
for i := 0; i < 5; i++ {
p.Put(resources[i])
}
if count.Get() != 5 {
t.Errorf("Expecting 5, received %d", count.Get())
}
if lastID.Get() != 6 {
t.Errorf("Expecting 6, received %d", lastID.Get())
}
// SetCapacity
p.SetCapacity(3)
if count.Get() != 3 {
t.Errorf("Expecting 3, received %d", count.Get())
}
if lastID.Get() != 6 {
t.Errorf("Expecting 6, received %d", lastID.Get())
}
capacity, available, _, _, _, _ := p.Stats()
if capacity != 3 {
t.Errorf("Expecting 3, received %d", capacity)
}
if available != 3 {
t.Errorf("Expecting 3, received %d", available)
}
p.SetCapacity(6)
capacity, available, _, _, _, _ = p.Stats()
if capacity != 6 {
t.Errorf("Expecting 6, received %d", capacity)
}
if available != 6 {
t.Errorf("Expecting 6, received %d", available)
}
for i := 0; i < 6; i++ {
r, err := p.Get(ctx)
if err != nil {
t.Errorf("Get failed: %v", err)
}
resources[i] = r
}
for i := 0; i < 6; i++ {
p.Put(resources[i])
}
if count.Get() != 6 {
t.Errorf("Expecting 5, received %d", count.Get())
}
if lastID.Get() != 9 {
t.Errorf("Expecting 9, received %d", lastID.Get())
}
// Close
p.Close()
capacity, available, _, _, _, _ = p.Stats()
if capacity != 0 {
t.Errorf("Expecting 0, received %d", capacity)
}
if available != 0 {
t.Errorf("Expecting 0, received %d", available)
}
if count.Get() != 0 {
t.Errorf("Expecting 0, received %d", count.Get())
}
}
func TestShrinking(t *testing.T) {
ctx := context.Background()
lastID.Set(0)
count.Set(0)
p := NewResourcePool(PoolFactory, 5, 5, time.Second)
var resources [10]Resource
// Leave one empty slot in the pool
for i := 0; i < 4; i++ {
r, err := p.Get(ctx)
if err != nil {
t.Errorf("Get failed: %v", err)
}
resources[i] = r
}
done := make(chan bool)
go func() {
p.SetCapacity(3)
done <- true
}()
expected := `{"Capacity": 3, "Available": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000}`
for i := 0; i < 10; i++ {
time.Sleep(10 * time.Millisecond)
stats := p.StatsJSON()
if stats != expected {
if i == 9 {
t.Errorf(`expecting '%s', received '%s'`, expected, stats)
}
}
}
// There are already 2 resources available in the pool.
// So, returning one should be enough for SetCapacity to complete.
p.Put(resources[3])
<-done
// Return the rest of the resources
for i := 0; i < 3; i++ {
p.Put(resources[i])
}
stats := p.StatsJSON()
expected = `{"Capacity": 3, "Available": 3, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000}`
if stats != expected {
t.Errorf(`expecting '%s', received '%s'`, expected, stats)
}
if count.Get() != 3 {
t.Errorf("Expecting 3, received %d", count.Get())
}
// Ensure no deadlock if SetCapacity is called after we start
// waiting for a resource
var err error
for i := 0; i < 3; i++ {
resources[i], err = p.Get(ctx)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
}
// This will wait because pool is empty
go func() {
r, err := p.Get(ctx)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
p.Put(r)
done <- true
}()
// This will also wait
go func() {
p.SetCapacity(2)
done <- true
}()
time.Sleep(10 * time.Millisecond)
// This should not hang
for i := 0; i < 3; i++ {
p.Put(resources[i])
}
<-done
<-done
capacity, available, _, waitcount, _, _ := p.Stats()
if capacity != 2 {
t.Errorf("Expecting 2, received %d", capacity)
}
if available != 2 {
t.Errorf("Expecting 2, received %d", available)
}
if waitcount != 1 {
t.Errorf("Expecting 1, received %d", waitcount)
}
if count.Get() != 2 {
t.Errorf("Expecting 2, received %d", count.Get())
}
// Test race condition of SetCapacity with itself
p.SetCapacity(3)
for i := 0; i < 3; i++ {
resources[i], err = p.Get(ctx)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
}
// This will wait because pool is empty
go func() {
r, err := p.Get(ctx)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
p.Put(r)
done <- true
}()
time.Sleep(10 * time.Nanosecond)
// This will wait till we Put
go p.SetCapacity(2)
time.Sleep(10 * time.Nanosecond)
go p.SetCapacity(4)
time.Sleep(10 * time.Nanosecond)
// This should not hang
for i := 0; i < 3; i++ {
p.Put(resources[i])
}
<-done
err = p.SetCapacity(-1)
if err == nil {
t.Errorf("Expecting error")
}
err = p.SetCapacity(255555)
if err == nil {
t.Errorf("Expecting error")
}
capacity, available, _, _, _, _ = p.Stats()
if capacity != 4 {
t.Errorf("Expecting 4, received %d", capacity)
}
if available != 4 {
t.Errorf("Expecting 4, received %d", available)
}
}
func TestClosing(t *testing.T) {
ctx := context.Background()
lastID.Set(0)
count.Set(0)
p := NewResourcePool(PoolFactory, 5, 5, time.Second)
var resources [10]Resource
for i := 0; i < 5; i++ {
r, err := p.Get(ctx)
if err != nil {
t.Errorf("Get failed: %v", err)
}
resources[i] = r
}
ch := make(chan bool)
go func() {
p.Close()
ch <- true
}()
// Wait for goroutine to call Close
time.Sleep(10 * time.Nanosecond)
stats := p.StatsJSON()
expected := `{"Capacity": 0, "Available": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000}`
if stats != expected {
t.Errorf(`expecting '%s', received '%s'`, expected, stats)
}
// Put is allowed when closing
for i := 0; i < 5; i++ {
p.Put(resources[i])
}
// Wait for Close to return
<-ch
// SetCapacity must be ignored after Close
err := p.SetCapacity(1)
if err == nil {
t.Errorf("expecting error")
}
stats = p.StatsJSON()
expected = `{"Capacity": 0, "Available": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000}`
if stats != expected {
t.Errorf(`expecting '%s', received '%s'`, expected, stats)
}
if lastID.Get() != 5 {
t.Errorf("Expecting 5, received %d", count.Get())
}
if count.Get() != 0 {
t.Errorf("Expecting 0, received %d", count.Get())
}
}
func TestIdleTimeout(t *testing.T) {
ctx := context.Background()
lastID.Set(0)
count.Set(0)
p := NewResourcePool(PoolFactory, 1, 1, 10*time.Nanosecond)
defer p.Close()
r, err := p.Get(ctx)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
p.Put(r)
if lastID.Get() != 1 {
t.Errorf("Expecting 1, received %d", count.Get())
}
if count.Get() != 1 {
t.Errorf("Expecting 1, received %d", count.Get())
}
time.Sleep(20 * time.Nanosecond)
r, err = p.Get(ctx)
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if lastID.Get() != 2 {
t.Errorf("Expecting 2, received %d", count.Get())
}
if count.Get() != 1 {
t.Errorf("Expecting 1, received %d", count.Get())
}
p.Put(r)
}
func TestCreateFail(t *testing.T) {
ctx := context.Background()
lastID.Set(0)
count.Set(0)
p := NewResourcePool(FailFactory, 5, 5, time.Second)
defer p.Close()
if _, err := p.Get(ctx); err.Error() != "Failed" {
t.Errorf("Expecting Failed, received %v", err)
}
stats := p.StatsJSON()
expected := `{"Capacity": 5, "Available": 5, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000}`
if stats != expected {
t.Errorf(`expecting '%s', received '%s'`, expected, stats)
}
}
func TestSlowCreateFail(t *testing.T) {
ctx := context.Background()
lastID.Set(0)
count.Set(0)
p := NewResourcePool(SlowFailFactory, 2, 2, time.Second)
defer p.Close()
ch := make(chan bool)
// The third Get should not wait indefinitely
for i := 0; i < 3; i++ {
go func() {
p.Get(ctx)
ch <- true
}()
}
for i := 0; i < 3; i++ {
<-ch
}
_, available, _, _, _, _ := p.Stats()
if available != 2 {
t.Errorf("Expecting 2, received %d", available)
}
}
func TestTimeout(t *testing.T) {
ctx := context.Background()
lastID.Set(0)
count.Set(0)
p := NewResourcePool(PoolFactory, 1, 1, time.Second)
defer p.Close()
r, err := p.Get(ctx)
if err != nil {
t.Fatal(err)
}
newctx, cancel := context.WithTimeout(ctx, 1*time.Millisecond)
_, err = p.Get(newctx)
cancel()
want := "resource pool timed out"
if err == nil || err.Error() != want {
t.Errorf("got %v, want %s", err, want)
}
p.Put(r)
}
func TestExpired(t *testing.T) {
lastID.Set(0)
count.Set(0)
p := NewResourcePool(PoolFactory, 1, 1, time.Second)
defer p.Close()
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-1*time.Second))
r, err := p.Get(ctx)
if err == nil {
p.Put(r)
}
cancel()
want := "resource pool timed out"
if err == nil || err.Error() != want {
t.Errorf("got %v, want %s", err, want)
}
}