зеркало из https://github.com/github/vitess-gh.git
492 строки
11 KiB
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)
|
|
}
|
|
}
|