vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/connpool/pool_test.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package connpool
    18  
    19  import (
    20  	"context"
    21  	"runtime"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  
    29  	"vitess.io/vitess/go/mysql/fakesqldb"
    30  	"vitess.io/vitess/go/pools"
    31  	"vitess.io/vitess/go/sqltypes"
    32  	"vitess.io/vitess/go/vt/callerid"
    33  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    34  )
    35  
    36  func TestConnPoolGet(t *testing.T) {
    37  	db := fakesqldb.New(t)
    38  	defer db.Close()
    39  	connPool := newPool()
    40  	connPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
    41  	defer connPool.Close()
    42  	dbConn, err := connPool.Get(context.Background(), nil)
    43  	if err != nil {
    44  		t.Fatalf("should not get an error, but got: %v", err)
    45  	}
    46  	if dbConn == nil {
    47  		t.Fatalf("db conn should not be nil")
    48  	}
    49  	// There is no context, it should not use appdebug connection
    50  	if dbConn.pool == nil {
    51  		t.Fatalf("db conn pool should not be nil")
    52  	}
    53  	dbConn.Recycle()
    54  }
    55  
    56  func TestConnPoolTimeout(t *testing.T) {
    57  	db := fakesqldb.New(t)
    58  	defer db.Close()
    59  	connPool := NewPool(tabletenv.NewEnv(nil, "PoolTest"), "TestPool", tabletenv.ConnPoolConfig{
    60  		Size:               1,
    61  		TimeoutSeconds:     1,
    62  		IdleTimeoutSeconds: 10,
    63  	})
    64  	connPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
    65  	defer connPool.Close()
    66  	dbConn, err := connPool.Get(context.Background(), nil)
    67  	require.NoError(t, err)
    68  	defer dbConn.Recycle()
    69  	_, err = connPool.Get(context.Background(), nil)
    70  	assert.EqualError(t, err, "resource pool timed out")
    71  }
    72  
    73  func TestConnPoolMaxWaiters(t *testing.T) {
    74  	db := fakesqldb.New(t)
    75  	defer db.Close()
    76  	connPool := NewPool(tabletenv.NewEnv(nil, "PoolTest"), "TestPool", tabletenv.ConnPoolConfig{
    77  		Size:       1,
    78  		MaxWaiters: 1,
    79  	})
    80  	connPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
    81  	defer connPool.Close()
    82  	dbConn, err := connPool.Get(context.Background(), nil)
    83  	require.NoError(t, err)
    84  
    85  	// waiter 1
    86  	var wg sync.WaitGroup
    87  	wg.Add(1)
    88  	go func() {
    89  		defer wg.Done()
    90  		c1, err := connPool.Get(context.Background(), nil)
    91  		if err != nil {
    92  			t.Errorf("unexpected error: %v", err)
    93  			return
    94  		}
    95  		c1.Recycle()
    96  	}()
    97  	// Wait for the first waiter to increment count.
    98  	for {
    99  		runtime.Gosched()
   100  		if connPool.waiterCount.Get() == 1 {
   101  			break
   102  		}
   103  	}
   104  
   105  	// waiter 2
   106  	_, err = connPool.Get(context.Background(), nil)
   107  	assert.EqualError(t, err, "pool TestPool waiter count exceeded")
   108  
   109  	// This recycle will make waiter1 succeed.
   110  	dbConn.Recycle()
   111  	wg.Wait()
   112  }
   113  
   114  func TestConnPoolGetEmptyDebugConfig(t *testing.T) {
   115  	db := fakesqldb.New(t)
   116  	debugConn := db.ConnParamsWithUname("")
   117  	defer db.Close()
   118  	connPool := newPool()
   119  	connPool.Open(db.ConnParams(), db.ConnParams(), debugConn)
   120  	im := callerid.NewImmediateCallerID("")
   121  	ecid := callerid.NewEffectiveCallerID("p", "c", "sc")
   122  	ctx := context.Background()
   123  	ctx = callerid.NewContext(ctx, ecid, im)
   124  	defer connPool.Close()
   125  	dbConn, err := connPool.Get(ctx, nil)
   126  	if err != nil {
   127  		t.Fatalf("should not get an error, but got: %v", err)
   128  	}
   129  	if dbConn == nil {
   130  		t.Fatalf("db conn should not be nil")
   131  	}
   132  	// Context is empty, it should not use appdebug connection
   133  	if dbConn.pool == nil {
   134  		t.Fatalf("db conn pool should not be nil")
   135  	}
   136  	dbConn.Recycle()
   137  }
   138  
   139  func TestConnPoolGetAppDebug(t *testing.T) {
   140  	db := fakesqldb.New(t)
   141  	debugConn := db.ConnParamsWithUname("debugUsername")
   142  	ctx := context.Background()
   143  	im := callerid.NewImmediateCallerID("debugUsername")
   144  	ecid := callerid.NewEffectiveCallerID("p", "c", "sc")
   145  	ctx = callerid.NewContext(ctx, ecid, im)
   146  	defer db.Close()
   147  	connPool := newPool()
   148  	connPool.Open(db.ConnParams(), db.ConnParams(), debugConn)
   149  	defer connPool.Close()
   150  	dbConn, err := connPool.Get(ctx, nil)
   151  	if err != nil {
   152  		t.Fatalf("should not get an error, but got: %v", err)
   153  	}
   154  	if dbConn == nil {
   155  		t.Fatalf("db conn should not be nil")
   156  	}
   157  	if dbConn.pool != nil {
   158  		t.Fatalf("db conn pool should be nil for appDebug")
   159  	}
   160  	dbConn.Recycle()
   161  	if !dbConn.IsClosed() {
   162  		t.Fatalf("db conn should be closed after recycle")
   163  	}
   164  }
   165  
   166  func TestConnPoolPutWhilePoolIsClosed(t *testing.T) {
   167  	connPool := newPool()
   168  	defer func() {
   169  		if recover() == nil {
   170  			t.Fatalf("pool is closed, should get an error")
   171  		}
   172  	}()
   173  	connPool.Put(nil)
   174  }
   175  
   176  func TestConnPoolSetCapacity(t *testing.T) {
   177  	db := fakesqldb.New(t)
   178  	defer db.Close()
   179  	connPool := newPool()
   180  	connPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   181  	defer connPool.Close()
   182  	err := connPool.SetCapacity(-10)
   183  	if err == nil {
   184  		t.Fatalf("set capacity should return error for negative capacity")
   185  	}
   186  	err = connPool.SetCapacity(10)
   187  	if err != nil {
   188  		t.Fatalf("set capacity should succeed")
   189  	}
   190  	if connPool.Capacity() != 10 {
   191  		t.Fatalf("capacity should be 10")
   192  	}
   193  }
   194  
   195  func TestConnPoolStatJSON(t *testing.T) {
   196  	db := fakesqldb.New(t)
   197  	defer db.Close()
   198  	connPool := newPool()
   199  	if connPool.StatsJSON() != "{}" {
   200  		t.Fatalf("pool is closed, stats json should be empty: {}")
   201  	}
   202  	connPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   203  	defer connPool.Close()
   204  	statsJSON := connPool.StatsJSON()
   205  	if statsJSON == "" || statsJSON == "{}" {
   206  		t.Fatalf("stats json should not be empty")
   207  	}
   208  }
   209  
   210  func TestConnPoolStateWhilePoolIsClosed(t *testing.T) {
   211  	connPool := newPool()
   212  	assert.EqualValues(t, 0, connPool.Capacity(), "pool capacity should be 0 because it is still closed")
   213  	assert.EqualValues(t, 0, connPool.Available(), "pool available connections should be 0 because it is still closed")
   214  	assert.EqualValues(t, 0, connPool.MaxCap(), "pool max capacity should be 0 because it is still closed")
   215  	assert.EqualValues(t, 0, connPool.WaitCount(), "pool wait count should be 0 because it is still closed")
   216  	assert.EqualValues(t, 0, connPool.WaitTime(), "pool wait time should be 0 because it is still closed")
   217  	assert.EqualValues(t, 0, connPool.IdleTimeout(), "pool idle timeout should be 0 because it is still closed")
   218  }
   219  
   220  func TestConnPoolStateWhilePoolIsOpen(t *testing.T) {
   221  	db := fakesqldb.New(t)
   222  	defer db.Close()
   223  	idleTimeout := 10 * time.Second
   224  	connPool := newPool()
   225  	connPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   226  	defer connPool.Close()
   227  	assert.EqualValues(t, 100, connPool.Capacity(), "pool capacity should be 100")
   228  	assert.EqualValues(t, 100, connPool.MaxCap(), "pool max capacity should be 100")
   229  	assert.EqualValues(t, 0, connPool.WaitTime(), "pool wait time should be 0")
   230  	assert.EqualValues(t, 0, connPool.WaitCount(), "pool wait count should be 0")
   231  	assert.EqualValues(t, idleTimeout, connPool.IdleTimeout(), "pool idle timeout should be 0")
   232  	assert.EqualValues(t, 100, connPool.Available(), "pool available connections should be 100")
   233  	assert.EqualValues(t, 0, connPool.Active(), "pool active connections should be 0")
   234  	assert.EqualValues(t, 0, connPool.InUse(), "pool inUse connections should be 0")
   235  
   236  	dbConn, _ := connPool.Get(context.Background(), nil)
   237  	assert.EqualValues(t, 99, connPool.Available(), "pool available connections should be 99")
   238  	assert.EqualValues(t, 1, connPool.Active(), "pool active connections should be 1")
   239  	assert.EqualValues(t, 1, connPool.InUse(), "pool inUse connections should be 1")
   240  
   241  	dbConn.Recycle()
   242  	assert.EqualValues(t, 100, connPool.Available(), "pool available connections should be 100")
   243  	assert.EqualValues(t, 1, connPool.Active(), "pool active connections should be 1")
   244  	assert.EqualValues(t, 0, connPool.InUse(), "pool inUse connections should be 0")
   245  }
   246  
   247  func TestConnPoolStateWithSettings(t *testing.T) {
   248  	db := fakesqldb.New(t)
   249  	defer db.Close()
   250  	capacity := 5
   251  	connPool := newPoolWithCapacity(capacity)
   252  	connPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   253  	defer connPool.Close()
   254  	assert.EqualValues(t, 5, connPool.Available(), "pool available connections should be 5")
   255  	assert.EqualValues(t, 0, connPool.Active(), "pool active connections should be 0")
   256  	assert.EqualValues(t, 0, connPool.InUse(), "pool inUse connections should be 0")
   257  	assert.EqualValues(t, 0, connPool.GetCount(), "pool get count should be 0")
   258  	assert.EqualValues(t, 0, connPool.GetSettingCount(), "pool get with settings should be 0")
   259  	assert.EqualValues(t, 0, connPool.DiffSettingCount(), "pool different settings count should be 0")
   260  	assert.EqualValues(t, 0, connPool.ResetSettingCount(), "pool reset settings count should be 0")
   261  
   262  	dbConn, err := connPool.Get(context.Background(), nil)
   263  	require.NoError(t, err)
   264  	assert.EqualValues(t, 4, connPool.Available(), "pool available connections should be 4")
   265  	assert.EqualValues(t, 1, connPool.Active(), "pool active connections should be 1")
   266  	assert.EqualValues(t, 1, connPool.InUse(), "pool inUse connections should be 1")
   267  	assert.EqualValues(t, 1, connPool.GetCount(), "pool get count should be 1")
   268  	assert.EqualValues(t, 0, connPool.GetSettingCount(), "pool get with settings should be 0")
   269  	assert.EqualValues(t, 0, connPool.DiffSettingCount(), "pool different settings count should be 0")
   270  	assert.EqualValues(t, 0, connPool.ResetSettingCount(), "pool reset settings count should be 0")
   271  
   272  	dbConn.Recycle()
   273  	assert.EqualValues(t, 5, connPool.Available(), "pool available connections should be 5")
   274  	assert.EqualValues(t, 1, connPool.Active(), "pool active connections should be 1")
   275  	assert.EqualValues(t, 0, connPool.InUse(), "pool inUse connections should be 0")
   276  	assert.EqualValues(t, 1, connPool.GetCount(), "pool get count should be 0")
   277  	assert.EqualValues(t, 0, connPool.GetSettingCount(), "pool get with settings should be 0")
   278  	assert.EqualValues(t, 0, connPool.DiffSettingCount(), "pool different settings count should be 0")
   279  	assert.EqualValues(t, 0, connPool.ResetSettingCount(), "pool reset settings count should be 0")
   280  
   281  	db.AddQuery("a", &sqltypes.Result{})
   282  	sa := pools.NewSetting("a", "")
   283  	dbConn, err = connPool.Get(context.Background(), sa)
   284  	require.NoError(t, err)
   285  	assert.EqualValues(t, 4, connPool.Available(), "pool available connections should be 4")
   286  	assert.EqualValues(t, 2, connPool.Active(), "pool active connections should be 2")
   287  	assert.EqualValues(t, 1, connPool.InUse(), "pool inUse connections should be 1")
   288  	assert.EqualValues(t, 1, connPool.GetCount(), "pool get count should be 1")
   289  	assert.EqualValues(t, 1, connPool.GetSettingCount(), "pool get with settings should be 1")
   290  	assert.EqualValues(t, 0, connPool.DiffSettingCount(), "pool different settings count should be 0")
   291  	assert.EqualValues(t, 0, connPool.ResetSettingCount(), "pool reset settings count should be 0")
   292  
   293  	dbConn.Recycle()
   294  	assert.EqualValues(t, 5, connPool.Available(), "pool available connections should be 5")
   295  	assert.EqualValues(t, 2, connPool.Active(), "pool active connections should be 2")
   296  	assert.EqualValues(t, 0, connPool.InUse(), "pool inUse connections should be 0")
   297  	assert.EqualValues(t, 1, connPool.GetCount(), "pool get count should be 1")
   298  	assert.EqualValues(t, 1, connPool.GetSettingCount(), "pool get with settings should be 1")
   299  	assert.EqualValues(t, 0, connPool.DiffSettingCount(), "pool different settings count should be 0")
   300  	assert.EqualValues(t, 0, connPool.ResetSettingCount(), "pool reset settings count should be 0")
   301  
   302  	// now showcasing diff and reset setting.
   303  	// Steps 1: acquire all connection with same setting
   304  	// Steps 2: put all back
   305  	// Steps 3: acquire a connection with no setting - this will show reset setting count
   306  	// Steps 4: acquire a connection with different setting - this will show diff setting count
   307  
   308  	// Step 1
   309  	var conns []*DBConn
   310  	for i := 0; i < capacity; i++ {
   311  		dbConn, err = connPool.Get(context.Background(), sa)
   312  		require.NoError(t, err)
   313  		conns = append(conns, dbConn)
   314  	}
   315  	assert.EqualValues(t, 0, connPool.Available(), "pool available connections should be 0")
   316  	assert.EqualValues(t, 5, connPool.Active(), "pool active connections should be 5")
   317  	assert.EqualValues(t, 5, connPool.InUse(), "pool inUse connections should be 5")
   318  	assert.EqualValues(t, 1, connPool.GetCount(), "pool get count should be 1")
   319  	assert.EqualValues(t, 6, connPool.GetSettingCount(), "pool get with settings should be 6")
   320  	assert.EqualValues(t, 0, connPool.DiffSettingCount(), "pool different settings count should be 0")
   321  	assert.EqualValues(t, 0, connPool.ResetSettingCount(), "pool reset settings count should be 0")
   322  
   323  	// Step 2
   324  	for _, conn := range conns {
   325  		conn.Recycle()
   326  	}
   327  	assert.EqualValues(t, 5, connPool.Available(), "pool available connections should be 5")
   328  	assert.EqualValues(t, 5, connPool.Active(), "pool active connections should be 5")
   329  	assert.EqualValues(t, 0, connPool.InUse(), "pool inUse connections should be 0")
   330  	assert.EqualValues(t, 1, connPool.GetCount(), "pool get count should be 1")
   331  	assert.EqualValues(t, 6, connPool.GetSettingCount(), "pool get with settings should be 6")
   332  	assert.EqualValues(t, 0, connPool.DiffSettingCount(), "pool different settings count should be 0")
   333  	assert.EqualValues(t, 0, connPool.ResetSettingCount(), "pool reset settings count should be 0")
   334  
   335  	// Step 3
   336  	dbConn, err = connPool.Get(context.Background(), nil)
   337  	require.NoError(t, err)
   338  	assert.EqualValues(t, 4, connPool.Available(), "pool available connections should be 4")
   339  	assert.EqualValues(t, 5, connPool.Active(), "pool active connections should be 5")
   340  	assert.EqualValues(t, 1, connPool.InUse(), "pool inUse connections should be 1")
   341  	assert.EqualValues(t, 2, connPool.GetCount(), "pool get count should be 2")
   342  	assert.EqualValues(t, 6, connPool.GetSettingCount(), "pool get with settings should be 6")
   343  	assert.EqualValues(t, 0, connPool.DiffSettingCount(), "pool different settings count should be 0")
   344  	assert.EqualValues(t, 1, connPool.ResetSettingCount(), "pool reset settings count should be 1")
   345  	dbConn.Recycle()
   346  
   347  	// Step 4
   348  	db.AddQuery("b", &sqltypes.Result{})
   349  	sb := pools.NewSetting("b", "")
   350  	dbConn, err = connPool.Get(context.Background(), sb)
   351  	require.NoError(t, err)
   352  	assert.EqualValues(t, 4, connPool.Available(), "pool available connections should be 4")
   353  	assert.EqualValues(t, 5, connPool.Active(), "pool active connections should be 5")
   354  	assert.EqualValues(t, 1, connPool.InUse(), "pool inUse connections should be 1")
   355  	assert.EqualValues(t, 2, connPool.GetCount(), "pool get count should be 2")
   356  	assert.EqualValues(t, 7, connPool.GetSettingCount(), "pool get with settings should be 7")
   357  	assert.EqualValues(t, 1, connPool.DiffSettingCount(), "pool different settings count should be 1")
   358  	assert.EqualValues(t, 1, connPool.ResetSettingCount(), "pool reset settings count should be 1")
   359  	dbConn.Recycle()
   360  }
   361  
   362  func TestPoolGetConnTime(t *testing.T) {
   363  	db := fakesqldb.New(t)
   364  	defer db.Close()
   365  
   366  	connPool := newPool()
   367  	connPool.Open(db.ConnParams(), db.ConnParams(), db.ConnParams())
   368  	defer connPool.Close()
   369  	connPool.getConnTime.Reset()
   370  
   371  	getTimeMap := connPool.getConnTime.Counts()
   372  	assert.Zero(t, getTimeMap["PoolTest.GetWithSettings"])
   373  	assert.Zero(t, getTimeMap["PoolTest.GetWithoutSettings"])
   374  
   375  	dbConn, err := connPool.Get(context.Background(), nil)
   376  	require.NoError(t, err)
   377  	defer dbConn.Recycle()
   378  
   379  	getTimeMap = connPool.getConnTime.Counts()
   380  	assert.EqualValues(t, 1, getTimeMap["PoolTest.GetWithoutSettings"])
   381  	assert.Zero(t, getTimeMap["PoolTest.GetWithSettings"])
   382  
   383  	db.AddQuery("b", &sqltypes.Result{})
   384  	sb := pools.NewSetting("b", "")
   385  	dbConn, err = connPool.Get(context.Background(), sb)
   386  	require.NoError(t, err)
   387  	defer dbConn.Recycle()
   388  
   389  	getTimeMap = connPool.getConnTime.Counts()
   390  	assert.EqualValues(t, 1, getTimeMap["PoolTest.GetWithSettings"])
   391  }
   392  
   393  func newPool() *Pool {
   394  	return newPoolWithCapacity(100)
   395  }
   396  
   397  func newPoolWithCapacity(capacity int) *Pool {
   398  	return NewPool(tabletenv.NewEnv(nil, "PoolTest"), "TestPool", tabletenv.ConnPoolConfig{
   399  		Size:               capacity,
   400  		IdleTimeoutSeconds: 10,
   401  	})
   402  }