gh-ost/vendor/github.com/ngaut/pools/resource_pool_test.go

488 lines
11 KiB
Go
Raw Normal View History

// 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/ngaut/sync2"
)
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 (tr *TestResource) IsClosed() bool {
return tr.closed
}
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) {
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()
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 TryGet
r, err := p.TryGet()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if r != nil {
t.Errorf("Expecting nil")
}
for i := 0; i < 5; i++ {
p.Put(resources[i])
_, available, _, _, _, _ := p.Stats()
if available != int64(i+1) {
t.Errorf("expecting %d, received %d", 5-i-1, available)
}
}
for i := 0; i < 5; i++ {
r, err := p.TryGet()
resources[i] = r
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if r == nil {
t.Errorf("Expecting non-nil")
}
if lastId.Get() != 5 {
t.Errorf("Expecting 5, received %d", lastId.Get())
}
if count.Get() != 5 {
t.Errorf("Expecting 5, received %d", count.Get())
}
}
// Test that Get waits
ch := make(chan bool)
go func() {
for i := 0; i < 5; i++ {
r, err := p.Get()
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.Nanosecond)
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()
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()
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()
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) {
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()
if err != nil {
t.Errorf("Get failed: %v", err)
}
resources[i] = r
}
go p.SetCapacity(3)
time.Sleep(10 * time.Nanosecond)
stats := p.StatsJSON()
expected := `{"Capacity": 3, "Available": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000}`
if stats != expected {
t.Errorf(`expecting '%s', received '%s'`, expected, stats)
}
// TryGet is allowed when shrinking
r, err := p.TryGet()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if r != nil {
t.Errorf("Expecting nil")
}
// Get is allowed when shrinking, but it will wait
getdone := make(chan bool)
go func() {
r, err := p.Get()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
p.Put(r)
getdone <- true
}()
// Put is allowed when shrinking. It's necessary.
for i := 0; i < 4; i++ {
p.Put(resources[i])
}
// Wait for Get test to complete
<-getdone
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
for i := 0; i < 3; i++ {
resources[i], err = p.Get()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
}
// This will wait because pool is empty
go func() {
r, err := p.Get()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
p.Put(r)
getdone <- true
}()
time.Sleep(10 * time.Nanosecond)
// This will wait till we Put
go p.SetCapacity(2)
time.Sleep(10 * time.Nanosecond)
// This should not hang
for i := 0; i < 3; i++ {
p.Put(resources[i])
}
<-getdone
capacity, available, _, _, _, _ := p.Stats()
if capacity != 2 {
t.Errorf("Expecting 2, received %d", capacity)
}
if available != 2 {
t.Errorf("Expecting 2, received %d", available)
}
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()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
}
// This will wait because pool is empty
go func() {
r, err := p.Get()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
p.Put(r)
getdone <- 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])
}
<-getdone
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) {
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()
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) {
lastId.Set(0)
count.Set(0)
p := NewResourcePool(PoolFactory, 1, 1, 10*time.Nanosecond)
defer p.Close()
r, err := p.Get()
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()
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) {
lastId.Set(0)
count.Set(0)
p := NewResourcePool(FailFactory, 5, 5, time.Second)
defer p.Close()
if _, err := p.Get(); 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) {
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()
ch <- true
}()
}
for i := 0; i < 3; i++ {
<-ch
}
_, available, _, _, _, _ := p.Stats()
if available != 2 {
t.Errorf("Expecting 2, received %d", available)
}
}