vitess.io/vitess@v0.16.2/go/pools/resource_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 pools
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  
    29  	"vitess.io/vitess/go/sync2"
    30  )
    31  
    32  var (
    33  	lastID, count, closeCount, resetCount sync2.AtomicInt64
    34  
    35  	waitStarts []time.Time
    36  
    37  	sFoo    = &Setting{query: "set foo=1"}
    38  	sBar    = &Setting{query: "set bar=1"}
    39  	sFooBar = &Setting{query: "set foo=1, bar=2"}
    40  )
    41  
    42  type TestResource struct {
    43  	num         int64
    44  	timeCreated time.Time
    45  	closed      bool
    46  	setting     string
    47  	failApply   bool
    48  }
    49  
    50  func (tr *TestResource) ResetSetting(ctx context.Context) error {
    51  	resetCount.Add(1)
    52  	tr.setting = ""
    53  	return nil
    54  }
    55  
    56  func (tr *TestResource) ApplySetting(ctx context.Context, setting *Setting) error {
    57  	if tr.failApply {
    58  		return fmt.Errorf("ApplySetting failed")
    59  	}
    60  	tr.setting = setting.query
    61  	return nil
    62  }
    63  
    64  func (tr *TestResource) IsSettingApplied() bool {
    65  	return len(tr.setting) > 0
    66  }
    67  
    68  func (tr *TestResource) IsSameSetting(setting string) bool {
    69  	return tr.setting == setting
    70  }
    71  
    72  func (tr *TestResource) Close() {
    73  	if !tr.closed {
    74  		count.Add(-1)
    75  		closeCount.Add(1)
    76  		tr.closed = true
    77  	}
    78  }
    79  
    80  var _ Resource = (*TestResource)(nil)
    81  
    82  func (tr *TestResource) Expired(lifetimeTimeout time.Duration) bool {
    83  	return lifetimeTimeout > 0 && time.Until(tr.timeCreated.Add(lifetimeTimeout)) < 0
    84  }
    85  
    86  func logWait(start time.Time) {
    87  	waitStarts = append(waitStarts, start)
    88  }
    89  
    90  func PoolFactory(context.Context) (Resource, error) {
    91  	count.Add(1)
    92  	return &TestResource{num: lastID.Add(1), timeCreated: time.Now()}, nil
    93  }
    94  
    95  func FailFactory(context.Context) (Resource, error) {
    96  	return nil, errors.New("Failed")
    97  }
    98  
    99  func SlowFailFactory(context.Context) (Resource, error) {
   100  	time.Sleep(10 * time.Millisecond)
   101  	return nil, errors.New("Failed")
   102  }
   103  
   104  func DisallowSettingsFactory(context.Context) (Resource, error) {
   105  	count.Add(1)
   106  	return &TestResource{num: lastID.Add(1), failApply: true}, nil
   107  }
   108  
   109  func TestOpen(t *testing.T) {
   110  	ctx := context.Background()
   111  	lastID.Set(0)
   112  	count.Set(0)
   113  	waitStarts = waitStarts[:0]
   114  
   115  	p := NewResourcePool(PoolFactory, 6, 6, time.Second, 0, logWait, nil, 0)
   116  	p.SetCapacity(5)
   117  	var resources [10]Resource
   118  	var r Resource
   119  	var err error
   120  
   121  	// Test Get
   122  	for i := 0; i < 5; i++ {
   123  		if i%2 == 0 {
   124  			r, err = p.Get(ctx, nil)
   125  		} else {
   126  			r, err = p.Get(ctx, sFoo)
   127  		}
   128  		require.NoError(t, err)
   129  		resources[i] = r
   130  		assert.EqualValues(t, 5-i-1, p.Available())
   131  		assert.Zero(t, p.WaitCount())
   132  		assert.Zero(t, len(waitStarts))
   133  		assert.Zero(t, p.WaitTime())
   134  		assert.EqualValues(t, i+1, lastID.Get())
   135  		assert.EqualValues(t, i+1, count.Get())
   136  	}
   137  
   138  	// Test that Get waits
   139  	ch := make(chan bool)
   140  	go func() {
   141  		for i := 0; i < 5; i++ {
   142  			if i%2 == 0 {
   143  				r, err = p.Get(ctx, nil)
   144  			} else {
   145  				r, err = p.Get(ctx, sFoo)
   146  			}
   147  			require.NoError(t, err)
   148  			resources[i] = r
   149  		}
   150  		for i := 0; i < 5; i++ {
   151  			p.Put(resources[i])
   152  		}
   153  		ch <- true
   154  	}()
   155  	for i := 0; i < 5; i++ {
   156  		// Sleep to ensure the goroutine waits
   157  		time.Sleep(10 * time.Millisecond)
   158  		p.Put(resources[i])
   159  	}
   160  	<-ch
   161  	assert.EqualValues(t, 5, p.WaitCount())
   162  	assert.Equal(t, 5, len(waitStarts))
   163  	// verify start times are monotonic increasing
   164  	for i := 1; i < len(waitStarts); i++ {
   165  		if waitStarts[i].Before(waitStarts[i-1]) {
   166  			t.Errorf("Expecting monotonic increasing start times")
   167  		}
   168  	}
   169  	assert.NotZero(t, p.WaitTime())
   170  	assert.EqualValues(t, 5, lastID.Get())
   171  	// Test Close resource
   172  	r, err = p.Get(ctx, nil)
   173  	require.NoError(t, err)
   174  	r.Close()
   175  	// A nil Put should cause the resource to be reopened.
   176  	p.Put(nil)
   177  	assert.EqualValues(t, 5, count.Get())
   178  	assert.EqualValues(t, 6, lastID.Get())
   179  
   180  	for i := 0; i < 5; i++ {
   181  		if i%2 == 0 {
   182  			r, err = p.Get(ctx, nil)
   183  		} else {
   184  			r, err = p.Get(ctx, sFoo)
   185  		}
   186  		require.NoError(t, err)
   187  		resources[i] = r
   188  	}
   189  	for i := 0; i < 5; i++ {
   190  		p.Put(resources[i])
   191  	}
   192  	assert.EqualValues(t, 5, count.Get())
   193  	assert.EqualValues(t, 6, lastID.Get())
   194  
   195  	// SetCapacity
   196  	p.SetCapacity(3)
   197  	assert.EqualValues(t, 3, count.Get())
   198  	assert.EqualValues(t, 6, lastID.Get())
   199  	assert.EqualValues(t, 3, p.Capacity())
   200  	assert.EqualValues(t, 3, p.Available())
   201  
   202  	p.SetCapacity(6)
   203  	assert.EqualValues(t, 6, p.Capacity())
   204  	assert.EqualValues(t, 6, p.Available())
   205  
   206  	for i := 0; i < 6; i++ {
   207  		if i%2 == 0 {
   208  			r, err = p.Get(ctx, nil)
   209  		} else {
   210  			r, err = p.Get(ctx, sFoo)
   211  		}
   212  		require.NoError(t, err)
   213  		resources[i] = r
   214  	}
   215  	for i := 0; i < 6; i++ {
   216  		p.Put(resources[i])
   217  	}
   218  	assert.EqualValues(t, 6, count.Get())
   219  	assert.EqualValues(t, 9, lastID.Get())
   220  
   221  	// Close
   222  	p.Close()
   223  	assert.EqualValues(t, 0, p.Capacity())
   224  	assert.EqualValues(t, 0, p.Available())
   225  	assert.EqualValues(t, 0, count.Get())
   226  }
   227  
   228  func TestShrinking(t *testing.T) {
   229  	ctx := context.Background()
   230  	lastID.Set(0)
   231  	count.Set(0)
   232  	waitStarts = waitStarts[:0]
   233  
   234  	p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
   235  	var resources [10]Resource
   236  	// Leave one empty slot in the pool
   237  	for i := 0; i < 4; i++ {
   238  		var r Resource
   239  		var err error
   240  		if i%2 == 0 {
   241  			r, err = p.Get(ctx, nil)
   242  		} else {
   243  			r, err = p.Get(ctx, sFoo)
   244  		}
   245  		require.NoError(t, err)
   246  		resources[i] = r
   247  	}
   248  	done := make(chan bool)
   249  	go func() {
   250  		p.SetCapacity(3)
   251  		done <- true
   252  	}()
   253  	expected := `{"Capacity": 3, "Available": 0, "Active": 4, "InUse": 4, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0, "MaxLifetimeClosed": 0, "Exhausted": 0}`
   254  	for i := 0; i < 10; i++ {
   255  		time.Sleep(10 * time.Millisecond)
   256  		stats := p.StatsJSON()
   257  		if stats != expected {
   258  			if i == 9 {
   259  				t.Errorf(`expecting '%s', received '%s'`, expected, stats)
   260  			}
   261  		}
   262  	}
   263  	// There are already 2 resources available in the pool.
   264  	// So, returning one should be enough for SetCapacity to complete.
   265  	p.Put(resources[3])
   266  	<-done
   267  	// Return the rest of the resources
   268  	for i := 0; i < 3; i++ {
   269  		p.Put(resources[i])
   270  	}
   271  	stats := p.StatsJSON()
   272  	expected = `{"Capacity": 3, "Available": 3, "Active": 3, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0, "MaxLifetimeClosed": 0, "Exhausted": 0}`
   273  	assert.Equal(t, expected, stats)
   274  	assert.EqualValues(t, 3, count.Get())
   275  
   276  	// Ensure no deadlock if SetCapacity is called after we start
   277  	// waiting for a resource
   278  	var err error
   279  	for i := 0; i < 3; i++ {
   280  		var r Resource
   281  		if i%2 == 0 {
   282  			r, err = p.Get(ctx, nil)
   283  		} else {
   284  			r, err = p.Get(ctx, sFoo)
   285  		}
   286  		require.NoError(t, err)
   287  		resources[i] = r
   288  	}
   289  	// This will wait because pool is empty
   290  	go func() {
   291  		r, err := p.Get(ctx, nil)
   292  		require.NoError(t, err)
   293  		p.Put(r)
   294  		done <- true
   295  	}()
   296  
   297  	// This will also wait
   298  	go func() {
   299  		p.SetCapacity(2)
   300  		done <- true
   301  	}()
   302  	time.Sleep(10 * time.Millisecond)
   303  
   304  	// This should not hang
   305  	for i := 0; i < 3; i++ {
   306  		p.Put(resources[i])
   307  	}
   308  	<-done
   309  	<-done
   310  	assert.EqualValues(t, 2, p.Capacity())
   311  	assert.EqualValues(t, 2, p.Available())
   312  	assert.EqualValues(t, 1, p.WaitCount())
   313  	assert.EqualValues(t, p.WaitCount(), len(waitStarts))
   314  	assert.EqualValues(t, 2, count.Get())
   315  
   316  	// Test race condition of SetCapacity with itself
   317  	p.SetCapacity(3)
   318  	for i := 0; i < 3; i++ {
   319  		var r Resource
   320  		var err error
   321  		if i%2 == 0 {
   322  			r, err = p.Get(ctx, nil)
   323  		} else {
   324  			r, err = p.Get(ctx, sFoo)
   325  		}
   326  		require.NoError(t, err)
   327  		resources[i] = r
   328  	}
   329  	// This will wait because pool is empty
   330  	go func() {
   331  		r, err := p.Get(ctx, nil)
   332  		require.NoError(t, err)
   333  		p.Put(r)
   334  		done <- true
   335  	}()
   336  	time.Sleep(10 * time.Millisecond)
   337  
   338  	// This will wait till we Put
   339  	go p.SetCapacity(2)
   340  	time.Sleep(10 * time.Millisecond)
   341  	go p.SetCapacity(4)
   342  	time.Sleep(10 * time.Millisecond)
   343  
   344  	// This should not hang
   345  	for i := 0; i < 3; i++ {
   346  		p.Put(resources[i])
   347  	}
   348  	<-done
   349  
   350  	err = p.SetCapacity(-1)
   351  	if err == nil {
   352  		t.Errorf("Expecting error")
   353  	}
   354  	err = p.SetCapacity(255555)
   355  	if err == nil {
   356  		t.Errorf("Expecting error")
   357  	}
   358  
   359  	assert.EqualValues(t, 4, p.Capacity())
   360  	assert.EqualValues(t, 4, p.Available())
   361  }
   362  
   363  func TestClosing(t *testing.T) {
   364  	ctx := context.Background()
   365  	lastID.Set(0)
   366  	count.Set(0)
   367  	p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
   368  	var resources [10]Resource
   369  	for i := 0; i < 5; i++ {
   370  		var r Resource
   371  		var err error
   372  		if i%2 == 0 {
   373  			r, err = p.Get(ctx, nil)
   374  		} else {
   375  			r, err = p.Get(ctx, sFoo)
   376  		}
   377  		require.NoError(t, err)
   378  		resources[i] = r
   379  	}
   380  	ch := make(chan bool)
   381  	go func() {
   382  		p.Close()
   383  		ch <- true
   384  	}()
   385  
   386  	// Wait for goroutine to call Close
   387  	time.Sleep(10 * time.Millisecond)
   388  	stats := p.StatsJSON()
   389  	expected := `{"Capacity": 0, "Available": 0, "Active": 5, "InUse": 5, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0, "MaxLifetimeClosed": 0, "Exhausted": 1}`
   390  	assert.Equal(t, expected, stats)
   391  
   392  	// Put is allowed when closing
   393  	for i := 0; i < 5; i++ {
   394  		p.Put(resources[i])
   395  	}
   396  
   397  	// Wait for Close to return
   398  	<-ch
   399  
   400  	stats = p.StatsJSON()
   401  	expected = `{"Capacity": 0, "Available": 0, "Active": 0, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0, "MaxLifetimeClosed": 0, "Exhausted": 1}`
   402  	assert.Equal(t, expected, stats)
   403  	assert.EqualValues(t, 5, lastID.Get())
   404  	assert.EqualValues(t, 0, count.Get())
   405  }
   406  
   407  func TestReopen(t *testing.T) {
   408  	ctx := context.Background()
   409  	lastID.Set(0)
   410  	count.Set(0)
   411  	refreshCheck := func() (bool, error) {
   412  		return true, nil
   413  	}
   414  	p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, refreshCheck, 500*time.Millisecond)
   415  	var resources [10]Resource
   416  	for i := 0; i < 5; i++ {
   417  		var r Resource
   418  		var err error
   419  		if i%2 == 0 {
   420  			r, err = p.Get(ctx, nil)
   421  		} else {
   422  			r, err = p.Get(ctx, sFoo)
   423  		}
   424  		require.NoError(t, err)
   425  		resources[i] = r
   426  	}
   427  
   428  	time.Sleep(10 * time.Millisecond)
   429  	stats := p.StatsJSON()
   430  	expected := `{"Capacity": 5, "Available": 0, "Active": 5, "InUse": 5, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0, "MaxLifetimeClosed": 0, "Exhausted": 1}`
   431  	assert.Equal(t, expected, stats)
   432  
   433  	time.Sleep(650 * time.Millisecond)
   434  	for i := 0; i < 5; i++ {
   435  		p.Put(resources[i])
   436  	}
   437  	time.Sleep(50 * time.Millisecond)
   438  	stats = p.StatsJSON()
   439  	expected = `{"Capacity": 5, "Available": 5, "Active": 0, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0, "MaxLifetimeClosed": 0, "Exhausted": 1}`
   440  	assert.Equal(t, expected, stats)
   441  	assert.EqualValues(t, 5, lastID.Get())
   442  	assert.EqualValues(t, 0, count.Get())
   443  }
   444  
   445  func TestIdleTimeout(t *testing.T) {
   446  	ctx := context.Background()
   447  	lastID.Set(0)
   448  	count.Set(0)
   449  	p := NewResourcePool(PoolFactory, 1, 1, 10*time.Millisecond, 0, logWait, nil, 0)
   450  	defer p.Close()
   451  
   452  	r, err := p.Get(ctx, nil)
   453  	require.NoError(t, err)
   454  	assert.EqualValues(t, 1, count.Get())
   455  	assert.EqualValues(t, 0, p.IdleClosed())
   456  
   457  	p.Put(r)
   458  	assert.EqualValues(t, 1, lastID.Get())
   459  	assert.EqualValues(t, 1, count.Get())
   460  	assert.EqualValues(t, 0, p.IdleClosed())
   461  
   462  	time.Sleep(15 * time.Millisecond)
   463  	assert.EqualValues(t, 1, count.Get())
   464  	assert.EqualValues(t, 1, p.IdleClosed())
   465  
   466  	r, err = p.Get(ctx, nil)
   467  	require.NoError(t, err)
   468  	assert.EqualValues(t, 2, lastID.Get())
   469  	assert.EqualValues(t, 1, count.Get())
   470  	assert.EqualValues(t, 1, p.IdleClosed())
   471  
   472  	// sleep to let the idle closer run while all resources are in use
   473  	// then make sure things are still as we expect
   474  	time.Sleep(15 * time.Millisecond)
   475  	assert.EqualValues(t, 2, lastID.Get())
   476  	assert.EqualValues(t, 1, count.Get())
   477  	assert.EqualValues(t, 1, p.IdleClosed())
   478  
   479  	p.Put(r)
   480  	r, err = p.Get(ctx, nil)
   481  	require.NoError(t, err)
   482  	assert.EqualValues(t, 2, lastID.Get())
   483  	assert.EqualValues(t, 1, count.Get())
   484  	assert.EqualValues(t, 1, p.IdleClosed())
   485  
   486  	// the idle close thread wakes up every 1/100 of the idle time, so ensure
   487  	// the timeout change applies to newly added resources
   488  	p.SetIdleTimeout(1000 * time.Millisecond)
   489  	p.Put(r)
   490  
   491  	time.Sleep(15 * time.Millisecond)
   492  	assert.EqualValues(t, 2, lastID.Get())
   493  	assert.EqualValues(t, 1, count.Get())
   494  	assert.EqualValues(t, 1, p.IdleClosed())
   495  
   496  	// Get and Put to refresh timeUsed
   497  	r, err = p.Get(ctx, nil)
   498  	require.NoError(t, err)
   499  	p.Put(r)
   500  	p.SetIdleTimeout(10 * time.Millisecond)
   501  	time.Sleep(15 * time.Millisecond)
   502  	assert.EqualValues(t, 3, lastID.Get())
   503  	assert.EqualValues(t, 1, count.Get())
   504  	assert.EqualValues(t, 2, p.IdleClosed())
   505  }
   506  
   507  func TestIdleTimeoutWithSettings(t *testing.T) {
   508  	ctx := context.Background()
   509  	lastID.Set(0)
   510  	count.Set(0)
   511  	p := NewResourcePool(PoolFactory, 1, 1, 10*time.Millisecond, 0, logWait, nil, 0)
   512  	defer p.Close()
   513  
   514  	r, err := p.Get(ctx, sFooBar)
   515  	require.NoError(t, err)
   516  	assert.EqualValues(t, 1, count.Get())
   517  	assert.EqualValues(t, 0, p.IdleClosed())
   518  
   519  	p.Put(r)
   520  	assert.EqualValues(t, 1, lastID.Get())
   521  	assert.EqualValues(t, 1, count.Get())
   522  	assert.EqualValues(t, 0, p.IdleClosed())
   523  
   524  	time.Sleep(15 * time.Millisecond)
   525  	assert.EqualValues(t, 1, count.Get())
   526  	assert.EqualValues(t, 1, p.IdleClosed())
   527  
   528  	r, err = p.Get(ctx, sFooBar)
   529  	require.NoError(t, err)
   530  	assert.EqualValues(t, 2, lastID.Get())
   531  	assert.EqualValues(t, 1, count.Get())
   532  	assert.EqualValues(t, 1, p.IdleClosed())
   533  
   534  	// sleep to let the idle closer run while all resources are in use
   535  	// then make sure things are still as we expect
   536  	time.Sleep(15 * time.Millisecond)
   537  	assert.EqualValues(t, 2, lastID.Get())
   538  	assert.EqualValues(t, 1, count.Get())
   539  	assert.EqualValues(t, 1, p.IdleClosed())
   540  
   541  	p.Put(r)
   542  	r, err = p.Get(ctx, sFooBar)
   543  	require.NoError(t, err)
   544  	assert.EqualValues(t, 2, lastID.Get())
   545  	assert.EqualValues(t, 1, count.Get())
   546  	assert.EqualValues(t, 1, p.IdleClosed())
   547  
   548  	// the idle close thread wakes up every 1/100 of the idle time, so ensure
   549  	// the timeout change applies to newly added resources
   550  	p.SetIdleTimeout(1000 * time.Millisecond)
   551  	p.Put(r)
   552  
   553  	time.Sleep(15 * time.Millisecond)
   554  	assert.EqualValues(t, 2, lastID.Get())
   555  	assert.EqualValues(t, 1, count.Get())
   556  	assert.EqualValues(t, 1, p.IdleClosed())
   557  
   558  	// Get and Put to refresh timeUsed
   559  	r, err = p.Get(ctx, sFooBar)
   560  	require.NoError(t, err)
   561  	p.Put(r)
   562  	p.SetIdleTimeout(10 * time.Millisecond)
   563  	time.Sleep(15 * time.Millisecond)
   564  	assert.EqualValues(t, 3, lastID.Get())
   565  	assert.EqualValues(t, 1, count.Get())
   566  	assert.EqualValues(t, 2, p.IdleClosed())
   567  }
   568  
   569  func TestIdleTimeoutCreateFail(t *testing.T) {
   570  	ctx := context.Background()
   571  	lastID.Set(0)
   572  	count.Set(0)
   573  	p := NewResourcePool(PoolFactory, 1, 1, 10*time.Millisecond, 0, logWait, nil, 0)
   574  	defer p.Close()
   575  	for _, setting := range []*Setting{nil, sFoo} {
   576  		r, err := p.Get(ctx, setting)
   577  		require.NoError(t, err)
   578  		// Change the factory before putting back
   579  		// to prevent race with the idle closer, who will
   580  		// try to use it.
   581  		p.factory = FailFactory
   582  		p.Put(r)
   583  		timeout := time.After(1 * time.Second)
   584  		for p.Active() != 0 {
   585  			select {
   586  			case <-timeout:
   587  				t.Errorf("Timed out waiting for resource to be closed by idle timeout")
   588  			default:
   589  			}
   590  		}
   591  		// reset factory for next run.
   592  		p.factory = PoolFactory
   593  	}
   594  }
   595  
   596  func TestMaxLifetime(t *testing.T) {
   597  	// maxLifetime 0
   598  	ctx := context.Background()
   599  	lastID.Set(0)
   600  	count.Set(0)
   601  
   602  	p := NewResourcePool(PoolFactory, 1, 1, 10*time.Second, 0, logWait, nil, 0)
   603  	defer p.Close()
   604  
   605  	r, err := p.Get(ctx, nil)
   606  	require.NoError(t, err)
   607  	assert.EqualValues(t, 1, count.Get())
   608  	assert.EqualValues(t, 0, p.MaxLifetimeClosed())
   609  
   610  	time.Sleep(10 * time.Millisecond)
   611  
   612  	p.Put(r)
   613  	assert.EqualValues(t, 1, lastID.Get())
   614  	assert.EqualValues(t, 1, count.Get())
   615  	assert.EqualValues(t, 0, p.MaxLifetimeClosed())
   616  
   617  	// maxLifetime > 0
   618  	ctx = context.Background()
   619  	lastID.Set(0)
   620  	count.Set(0)
   621  
   622  	p = NewResourcePool(PoolFactory, 1, 1, 10*time.Second, 10*time.Millisecond, logWait, nil, 0)
   623  	defer p.Close()
   624  
   625  	r, err = p.Get(ctx, nil)
   626  	require.NoError(t, err)
   627  	assert.EqualValues(t, 1, count.Get())
   628  	assert.EqualValues(t, 0, p.MaxLifetimeClosed())
   629  
   630  	time.Sleep(5 * time.Millisecond)
   631  
   632  	p.Put(r)
   633  	assert.EqualValues(t, 1, lastID.Get())
   634  	assert.EqualValues(t, 1, count.Get())
   635  	assert.EqualValues(t, 0, p.MaxLifetimeClosed())
   636  
   637  	r, err = p.Get(ctx, nil)
   638  	require.NoError(t, err)
   639  	assert.EqualValues(t, 1, count.Get())
   640  	assert.EqualValues(t, 0, p.MaxLifetimeClosed())
   641  
   642  	time.Sleep(10 * time.Millisecond * 2)
   643  
   644  	p.Put(r)
   645  	assert.EqualValues(t, 2, lastID.Get())
   646  	assert.EqualValues(t, 1, count.Get())
   647  	assert.EqualValues(t, 1, p.MaxLifetimeClosed())
   648  }
   649  
   650  func TestExtendedLifetimeTimeout(t *testing.T) {
   651  	// maxLifetime 0
   652  	p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
   653  	defer p.Close()
   654  	assert.Zero(t, p.extendedMaxLifetime())
   655  
   656  	// maxLifetime > 0
   657  	maxLifetime := 10 * time.Millisecond
   658  	for i := 0; i < 10; i++ {
   659  		p = NewResourcePool(PoolFactory, 5, 5, time.Second, maxLifetime, logWait, nil, 0)
   660  		defer p.Close()
   661  		assert.LessOrEqual(t, maxLifetime, p.extendedMaxLifetime())
   662  		assert.Greater(t, 2*maxLifetime, p.extendedMaxLifetime())
   663  	}
   664  }
   665  
   666  func TestCreateFail(t *testing.T) {
   667  	ctx := context.Background()
   668  	lastID.Set(0)
   669  	count.Set(0)
   670  	p := NewResourcePool(FailFactory, 5, 5, time.Second, 0, logWait, nil, 0)
   671  	defer p.Close()
   672  
   673  	for _, setting := range []*Setting{nil, sFoo} {
   674  		if _, err := p.Get(ctx, setting); err.Error() != "Failed" {
   675  			t.Errorf("Expecting Failed, received %v", err)
   676  		}
   677  		stats := p.StatsJSON()
   678  		expected := `{"Capacity": 5, "Available": 5, "Active": 0, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0, "MaxLifetimeClosed": 0, "Exhausted": 0}`
   679  		assert.Equal(t, expected, stats)
   680  	}
   681  }
   682  
   683  func TestCreateFailOnPut(t *testing.T) {
   684  	ctx := context.Background()
   685  	lastID.Set(0)
   686  	count.Set(0)
   687  	p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
   688  	defer p.Close()
   689  
   690  	for _, setting := range []*Setting{nil, sFoo} {
   691  		_, err := p.Get(ctx, setting)
   692  		require.NoError(t, err)
   693  
   694  		// change factory to fail the put.
   695  		p.factory = FailFactory
   696  		p.Put(nil)
   697  		assert.Zero(t, p.Active())
   698  
   699  		// change back for next iteration.
   700  		p.factory = PoolFactory
   701  	}
   702  }
   703  
   704  func TestSlowCreateFail(t *testing.T) {
   705  	ctx := context.Background()
   706  	lastID.Set(0)
   707  	count.Set(0)
   708  	p := NewResourcePool(SlowFailFactory, 2, 2, time.Second, 0, logWait, nil, 0)
   709  	defer p.Close()
   710  	ch := make(chan bool)
   711  	for _, setting := range []*Setting{nil, sFoo} {
   712  		// The third Get should not wait indefinitely
   713  		for i := 0; i < 3; i++ {
   714  			go func() {
   715  				p.Get(ctx, setting)
   716  				ch <- true
   717  			}()
   718  		}
   719  		for i := 0; i < 3; i++ {
   720  			<-ch
   721  		}
   722  		assert.EqualValues(t, 2, p.Available())
   723  	}
   724  }
   725  
   726  func TestTimeout(t *testing.T) {
   727  	ctx := context.Background()
   728  	lastID.Set(0)
   729  	count.Set(0)
   730  	p := NewResourcePool(PoolFactory, 1, 1, time.Second, 0, logWait, nil, 0)
   731  	defer p.Close()
   732  
   733  	// take the only connection available
   734  	r, err := p.Get(ctx, nil)
   735  	require.NoError(t, err)
   736  
   737  	for _, setting := range []*Setting{nil, sFoo} {
   738  		// trying to get the connection without a timeout.
   739  		newctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
   740  		_, err = p.Get(newctx, setting)
   741  		cancel()
   742  		assert.EqualError(t, err, "resource pool timed out")
   743  
   744  	}
   745  
   746  	// put the connection take was taken initially.
   747  	p.Put(r)
   748  }
   749  
   750  func TestExpired(t *testing.T) {
   751  	lastID.Set(0)
   752  	count.Set(0)
   753  	p := NewResourcePool(PoolFactory, 1, 1, time.Second, 0, logWait, nil, 0)
   754  	defer p.Close()
   755  
   756  	for _, setting := range []*Setting{nil, sFoo} {
   757  		// expired context
   758  		ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-1*time.Second))
   759  		_, err := p.Get(ctx, setting)
   760  		cancel()
   761  		require.EqualError(t, err, "resource pool context already expired")
   762  	}
   763  }
   764  
   765  func TestMultiSettings(t *testing.T) {
   766  	ctx := context.Background()
   767  	lastID.Set(0)
   768  	count.Set(0)
   769  	waitStarts = waitStarts[:0]
   770  
   771  	p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
   772  	var resources [10]Resource
   773  	var r Resource
   774  	var err error
   775  
   776  	settings := []*Setting{nil, sFoo, sBar, sBar, sFoo}
   777  
   778  	// Test Get
   779  	for i := 0; i < 5; i++ {
   780  		r, err = p.Get(ctx, settings[i])
   781  		require.NoError(t, err)
   782  		resources[i] = r
   783  		assert.EqualValues(t, 5-i-1, p.Available())
   784  		assert.Zero(t, p.WaitCount())
   785  		assert.Zero(t, len(waitStarts))
   786  		assert.Zero(t, p.WaitTime())
   787  		assert.EqualValues(t, i+1, lastID.Get())
   788  		assert.EqualValues(t, i+1, count.Get())
   789  	}
   790  
   791  	// Test that Get waits
   792  	ch := make(chan bool)
   793  	go func() {
   794  		for i := 0; i < 5; i++ {
   795  			r, err = p.Get(ctx, settings[i])
   796  			require.NoError(t, err)
   797  			resources[i] = r
   798  		}
   799  		for i := 0; i < 5; i++ {
   800  			p.Put(resources[i])
   801  		}
   802  		ch <- true
   803  	}()
   804  	for i := 0; i < 5; i++ {
   805  		// Sleep to ensure the goroutine waits
   806  		time.Sleep(10 * time.Millisecond)
   807  		p.Put(resources[i])
   808  	}
   809  	<-ch
   810  	assert.EqualValues(t, 5, p.WaitCount())
   811  	assert.Equal(t, 5, len(waitStarts))
   812  	// verify start times are monotonic increasing
   813  	for i := 1; i < len(waitStarts); i++ {
   814  		if waitStarts[i].Before(waitStarts[i-1]) {
   815  			t.Errorf("Expecting monotonic increasing start times")
   816  		}
   817  	}
   818  	assert.NotZero(t, p.WaitTime())
   819  	assert.EqualValues(t, 5, lastID.Get())
   820  
   821  	// Close
   822  	p.Close()
   823  	assert.EqualValues(t, 0, p.Capacity())
   824  	assert.EqualValues(t, 0, p.Available())
   825  	assert.EqualValues(t, 0, count.Get())
   826  }
   827  
   828  func TestMultiSettingsWithReset(t *testing.T) {
   829  	ctx := context.Background()
   830  	lastID.Set(0)
   831  	count.Set(0)
   832  	resetCount.Set(0)
   833  
   834  	p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
   835  	var resources [10]Resource
   836  	var r Resource
   837  	var err error
   838  
   839  	settings := []*Setting{nil, sFoo, sBar, sBar, sFoo}
   840  
   841  	// Test Get
   842  	for i := 0; i < 5; i++ {
   843  		r, err = p.Get(ctx, settings[i])
   844  		require.NoError(t, err)
   845  		resources[i] = r
   846  		assert.EqualValues(t, 5-i-1, p.Available())
   847  		assert.EqualValues(t, i+1, lastID.Get())
   848  		assert.EqualValues(t, i+1, count.Get())
   849  	}
   850  
   851  	// Put all of them back
   852  	for i := 0; i < 5; i++ {
   853  		p.Put(resources[i])
   854  	}
   855  
   856  	// Getting all with same setting.
   857  	for i := 0; i < 5; i++ {
   858  		r, err = p.Get(ctx, settings[1]) // {foo}
   859  		require.NoError(t, err)
   860  		p.Put(r)
   861  	}
   862  	assert.EqualValues(t, 2, resetCount.Get()) // when setting was {bar} and getting for {foo}
   863  	assert.EqualValues(t, 5, p.Available())
   864  	assert.EqualValues(t, 5, lastID.Get())
   865  	assert.EqualValues(t, 5, count.Get())
   866  
   867  	// Close
   868  	p.Close()
   869  	assert.EqualValues(t, 0, p.Capacity())
   870  	assert.EqualValues(t, 0, p.Available())
   871  	assert.EqualValues(t, 0, count.Get())
   872  }
   873  
   874  func TestApplySettingsFailure(t *testing.T) {
   875  	ctx := context.Background()
   876  	var resources []Resource
   877  	var r Resource
   878  	var err error
   879  
   880  	p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
   881  	defer p.Close()
   882  
   883  	settings := []*Setting{nil, sFoo, sBar, sBar, sFoo}
   884  	// get the resource and mark for failure
   885  	for i := 0; i < 5; i++ {
   886  		r, err = p.Get(ctx, settings[i])
   887  		require.NoError(t, err)
   888  		r.(*TestResource).failApply = true
   889  		resources = append(resources, r)
   890  	}
   891  	// put them back
   892  	for _, r = range resources {
   893  		p.Put(r)
   894  	}
   895  
   896  	// any new connection created will fail to apply setting
   897  	p.factory = DisallowSettingsFactory
   898  
   899  	// Get the resource with "foo" setting
   900  	// For an applied connection if the setting are same it will be returned as-is.
   901  	// Otherwise, will fail to get the resource.
   902  	var failCount int
   903  	resources = nil
   904  	for i := 0; i < 5; i++ {
   905  		r, err = p.Get(ctx, settings[1])
   906  		if err != nil {
   907  			failCount++
   908  			assert.EqualError(t, err, "ApplySetting failed")
   909  			continue
   910  		}
   911  		resources = append(resources, r)
   912  	}
   913  	// put them back
   914  	for _, r = range resources {
   915  		p.Put(r)
   916  	}
   917  	require.Equal(t, 3, failCount)
   918  
   919  	// should be able to get all the resource with no setting
   920  	resources = nil
   921  	for i := 0; i < 5; i++ {
   922  		r, err = p.Get(ctx, nil)
   923  		require.NoError(t, err)
   924  		resources = append(resources, r)
   925  	}
   926  	// put them back
   927  	for _, r = range resources {
   928  		p.Put(r)
   929  	}
   930  }