vitess.io/vitess@v0.16.2/go/pools/resource_pool.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 provides functionality to manage and reuse resources
    18  // like connections.
    19  package pools
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"math/rand"
    26  	"sync"
    27  	"time"
    28  
    29  	"vitess.io/vitess/go/sync2"
    30  	"vitess.io/vitess/go/timer"
    31  	"vitess.io/vitess/go/vt/log"
    32  	"vitess.io/vitess/go/vt/vterrors"
    33  
    34  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    35  )
    36  
    37  type (
    38  	IResourcePool interface {
    39  		Close()
    40  		Name() string
    41  		Get(ctx context.Context, setting *Setting) (resource Resource, err error)
    42  		Put(resource Resource)
    43  		SetCapacity(capacity int) error
    44  		SetIdleTimeout(idleTimeout time.Duration)
    45  		StatsJSON() string
    46  		Capacity() int64
    47  		Available() int64
    48  		Active() int64
    49  		InUse() int64
    50  		MaxCap() int64
    51  		WaitCount() int64
    52  		WaitTime() time.Duration
    53  		IdleTimeout() time.Duration
    54  		IdleClosed() int64
    55  		MaxLifetimeClosed() int64
    56  		Exhausted() int64
    57  		GetCount() int64
    58  		GetSettingCount() int64
    59  		DiffSettingCount() int64
    60  		ResetSettingCount() int64
    61  	}
    62  
    63  	// Resource defines the interface that every resource must provide.
    64  	// Thread synchronization between Close() and IsClosed()
    65  	// is the responsibility of the caller.
    66  	Resource interface {
    67  		Close()
    68  		Expired(time.Duration) bool
    69  		ApplySetting(ctx context.Context, setting *Setting) error
    70  		IsSettingApplied() bool
    71  		IsSameSetting(setting string) bool
    72  		ResetSetting(ctx context.Context) error
    73  	}
    74  
    75  	// Factory is a function that can be used to create a resource.
    76  	Factory func(context.Context) (Resource, error)
    77  
    78  	resourceWrapper struct {
    79  		resource Resource
    80  		timeUsed time.Time
    81  	}
    82  
    83  	// Setting represents a set query and reset query for system settings.
    84  	Setting struct {
    85  		query      string
    86  		resetQuery string
    87  	}
    88  
    89  	// ResourcePool allows you to use a pool of resources.
    90  	ResourcePool struct {
    91  		// stats. Atomic fields must remain at the top in order to prevent panics on certain architectures.
    92  		available         sync2.AtomicInt64
    93  		active            sync2.AtomicInt64
    94  		inUse             sync2.AtomicInt64
    95  		waitCount         sync2.AtomicInt64
    96  		waitTime          sync2.AtomicDuration
    97  		idleClosed        sync2.AtomicInt64
    98  		maxLifetimeClosed sync2.AtomicInt64
    99  		exhausted         sync2.AtomicInt64
   100  
   101  		capacity    sync2.AtomicInt64
   102  		idleTimeout sync2.AtomicDuration
   103  		maxLifetime sync2.AtomicDuration
   104  
   105  		resources chan resourceWrapper
   106  		factory   Factory
   107  		idleTimer *timer.Timer
   108  		logWait   func(time.Time)
   109  
   110  		settingResources  chan resourceWrapper
   111  		getCount          sync2.AtomicInt64
   112  		getSettingCount   sync2.AtomicInt64
   113  		diffSettingCount  sync2.AtomicInt64
   114  		resetSettingCount sync2.AtomicInt64
   115  
   116  		reopenMutex sync.Mutex
   117  		refresh     *poolRefresh
   118  	}
   119  )
   120  
   121  var (
   122  	// ErrClosed is returned if ResourcePool is used when it's closed.
   123  	ErrClosed = errors.New("resource pool is closed")
   124  
   125  	// ErrTimeout is returned if a resource get times out.
   126  	ErrTimeout = vterrors.New(vtrpcpb.Code_RESOURCE_EXHAUSTED, "resource pool timed out")
   127  
   128  	// ErrCtxTimeout is returned if a ctx is already expired by the time the resource pool is used
   129  	ErrCtxTimeout = vterrors.New(vtrpcpb.Code_DEADLINE_EXCEEDED, "resource pool context already expired")
   130  )
   131  
   132  func NewSetting(query, resetQuery string) *Setting {
   133  	return &Setting{
   134  		query:      query,
   135  		resetQuery: resetQuery,
   136  	}
   137  }
   138  
   139  func (s *Setting) GetQuery() string {
   140  	return s.query
   141  }
   142  
   143  func (s *Setting) GetResetQuery() string {
   144  	return s.resetQuery
   145  }
   146  
   147  // NewResourcePool creates a new ResourcePool pool.
   148  // capacity is the number of possible resources in the pool:
   149  // there can be up to 'capacity' of these at a given time.
   150  // maxCap specifies the extent to which the pool can be resized
   151  // in the future through the SetCapacity function.
   152  // You cannot resize the pool beyond maxCap.
   153  // If a resource is unused beyond idleTimeout, it's replaced
   154  // with a new one.
   155  // An idleTimeout of 0 means that there is no timeout.
   156  // An maxLifetime of 0 means that there is no timeout.
   157  // A non-zero value of prefillParallelism causes the pool to be pre-filled.
   158  // The value specifies how many resources can be opened in parallel.
   159  // refreshCheck is a function we consult at refreshInterval
   160  // intervals to determine if the pool should be drained and reopened
   161  func NewResourcePool(factory Factory, capacity, maxCap int, idleTimeout time.Duration, maxLifetime time.Duration, logWait func(time.Time), refreshCheck RefreshCheck, refreshInterval time.Duration) *ResourcePool {
   162  	if capacity <= 0 || maxCap <= 0 || capacity > maxCap {
   163  		panic(errors.New("invalid/out of range capacity"))
   164  	}
   165  	rp := &ResourcePool{
   166  		resources:        make(chan resourceWrapper, maxCap),
   167  		settingResources: make(chan resourceWrapper, maxCap),
   168  		factory:          factory,
   169  		available:        sync2.NewAtomicInt64(int64(capacity)),
   170  		capacity:         sync2.NewAtomicInt64(int64(capacity)),
   171  		idleTimeout:      sync2.NewAtomicDuration(idleTimeout),
   172  		maxLifetime:      sync2.NewAtomicDuration(maxLifetime),
   173  		logWait:          logWait,
   174  	}
   175  	for i := 0; i < capacity; i++ {
   176  		rp.resources <- resourceWrapper{}
   177  	}
   178  
   179  	if idleTimeout != 0 {
   180  		rp.idleTimer = timer.NewTimer(idleTimeout / 10)
   181  		rp.idleTimer.Start(rp.closeIdleResources)
   182  	}
   183  
   184  	rp.refresh = newPoolRefresh(rp, refreshCheck, refreshInterval)
   185  	rp.refresh.startRefreshTicker()
   186  
   187  	return rp
   188  }
   189  
   190  func (rp *ResourcePool) Name() string {
   191  	return "ResourcePool"
   192  }
   193  
   194  // Close empties the pool calling Close on all its resources.
   195  // You can call Close while there are outstanding resources.
   196  // It waits for all resources to be returned (Put).
   197  // After a Close, Get is not allowed.
   198  func (rp *ResourcePool) Close() {
   199  	if rp.idleTimer != nil {
   200  		rp.idleTimer.Stop()
   201  	}
   202  	rp.refresh.stop()
   203  	_ = rp.SetCapacity(0)
   204  }
   205  
   206  // closeIdleResources scans the pool for idle resources
   207  func (rp *ResourcePool) closeIdleResources() {
   208  	available := int(rp.Available())
   209  	idleTimeout := rp.IdleTimeout()
   210  
   211  	for i := 0; i < available; i++ {
   212  		var wrapper resourceWrapper
   213  		var origPool bool
   214  		select {
   215  		case wrapper = <-rp.resources:
   216  			origPool = true
   217  		case wrapper = <-rp.settingResources:
   218  			origPool = false
   219  		default:
   220  			// stop early if we don't get anything new from the pool
   221  			return
   222  		}
   223  
   224  		var reopened bool
   225  		if wrapper.resource != nil && idleTimeout > 0 && time.Until(wrapper.timeUsed.Add(idleTimeout)) < 0 {
   226  			wrapper.resource.Close()
   227  			rp.idleClosed.Add(1)
   228  			rp.reopenResource(&wrapper)
   229  			reopened = true
   230  		}
   231  		rp.returnResource(&wrapper, origPool, reopened)
   232  	}
   233  }
   234  
   235  func (rp *ResourcePool) returnResource(wrapper *resourceWrapper, origPool bool, reopened bool) {
   236  	if origPool || reopened {
   237  		rp.resources <- *wrapper
   238  	} else {
   239  		rp.settingResources <- *wrapper
   240  	}
   241  }
   242  
   243  // reopen drains and reopens the connection pool
   244  func (rp *ResourcePool) reopen() {
   245  	rp.reopenMutex.Lock() // Avoid race, since we can refresh asynchronously
   246  	defer rp.reopenMutex.Unlock()
   247  	capacity := int(rp.capacity.Get())
   248  	log.Infof("Draining and reopening resource pool with capacity %d by request", capacity)
   249  	rp.Close()
   250  	_ = rp.SetCapacity(capacity)
   251  	if rp.idleTimer != nil {
   252  		rp.idleTimer.Start(rp.closeIdleResources)
   253  	}
   254  	rp.refresh.startRefreshTicker()
   255  }
   256  
   257  // Get will return the next available resource. If capacity
   258  // has not been reached, it will create a new one using the factory. Otherwise,
   259  // it will wait till the next resource becomes available or a timeout.
   260  // A timeout of 0 is an indefinite wait.
   261  func (rp *ResourcePool) Get(ctx context.Context, setting *Setting) (resource Resource, err error) {
   262  	// If ctx has already expired, avoid racing with rp's resource channel.
   263  	if ctx.Err() != nil {
   264  		return nil, ErrCtxTimeout
   265  	}
   266  	if setting == nil {
   267  		return rp.get(ctx)
   268  	}
   269  	return rp.getWithSettings(ctx, setting)
   270  }
   271  
   272  func (rp *ResourcePool) get(ctx context.Context) (resource Resource, err error) {
   273  	rp.getCount.Add(1)
   274  	// Fetch
   275  	var wrapper resourceWrapper
   276  	var ok bool
   277  	// If we put both the channel together, then, go select can read from any channel
   278  	// this way we guarantee it will try to read from the channel we intended to read it from first
   279  	// and then try to read from next best available resource.
   280  	select {
   281  	// check normal resources first
   282  	case wrapper, ok = <-rp.resources:
   283  	default:
   284  		select {
   285  		// then checking setting resources
   286  		case wrapper, ok = <-rp.settingResources:
   287  		default:
   288  			// now waiting
   289  			startTime := time.Now()
   290  			select {
   291  			case wrapper, ok = <-rp.resources:
   292  			case wrapper, ok = <-rp.settingResources:
   293  			case <-ctx.Done():
   294  				return nil, ErrTimeout
   295  			}
   296  			rp.recordWait(startTime)
   297  		}
   298  	}
   299  	if !ok {
   300  		return nil, ErrClosed
   301  	}
   302  
   303  	// if the resource has setting applied, we will close it and return a new one
   304  	if wrapper.resource != nil && wrapper.resource.IsSettingApplied() {
   305  		rp.resetSettingCount.Add(1)
   306  		err = wrapper.resource.ResetSetting(ctx)
   307  		if err != nil {
   308  			// as reset is unsuccessful, we will close this resource
   309  			wrapper.resource.Close()
   310  			wrapper.resource = nil
   311  			rp.active.Add(-1)
   312  		}
   313  	}
   314  
   315  	// Unwrap
   316  	if wrapper.resource == nil {
   317  		wrapper.resource, err = rp.factory(ctx)
   318  		if err != nil {
   319  			rp.resources <- resourceWrapper{}
   320  			return nil, err
   321  		}
   322  		rp.active.Add(1)
   323  	}
   324  	if rp.available.Add(-1) <= 0 {
   325  		rp.exhausted.Add(1)
   326  	}
   327  	rp.inUse.Add(1)
   328  	return wrapper.resource, err
   329  }
   330  
   331  func (rp *ResourcePool) getWithSettings(ctx context.Context, setting *Setting) (Resource, error) {
   332  	rp.getSettingCount.Add(1)
   333  	var wrapper resourceWrapper
   334  	var ok bool
   335  	var err error
   336  
   337  	// Fetch
   338  	select {
   339  	// check setting resources first
   340  	case wrapper, ok = <-rp.settingResources:
   341  	default:
   342  		select {
   343  		// then, check normal resources
   344  		case wrapper, ok = <-rp.resources:
   345  		default:
   346  			// now waiting
   347  			startTime := time.Now()
   348  			select {
   349  			case wrapper, ok = <-rp.settingResources:
   350  			case wrapper, ok = <-rp.resources:
   351  			case <-ctx.Done():
   352  				return nil, ErrTimeout
   353  			}
   354  			rp.recordWait(startTime)
   355  		}
   356  	}
   357  	if !ok {
   358  		return nil, ErrClosed
   359  	}
   360  
   361  	// Checking setting hash id, if it is different, we will close the resource and return a new one later in unwrap
   362  	if wrapper.resource != nil && wrapper.resource.IsSettingApplied() && !wrapper.resource.IsSameSetting(setting.query) {
   363  		rp.diffSettingCount.Add(1)
   364  		err = wrapper.resource.ResetSetting(ctx)
   365  		if err != nil {
   366  			// as reset is unsuccessful, we will close this resource
   367  			wrapper.resource.Close()
   368  			wrapper.resource = nil
   369  			rp.active.Add(-1)
   370  		}
   371  	}
   372  
   373  	// Unwrap
   374  	if wrapper.resource == nil {
   375  		wrapper.resource, err = rp.factory(ctx)
   376  		if err != nil {
   377  			rp.resources <- resourceWrapper{}
   378  			return nil, err
   379  		}
   380  		rp.active.Add(1)
   381  	}
   382  
   383  	if !wrapper.resource.IsSettingApplied() {
   384  		if err = wrapper.resource.ApplySetting(ctx, setting); err != nil {
   385  			// as we are not able to apply setting, we can return this connection to non-setting channel.
   386  			// TODO: may check the error code to see if it is recoverable or not.
   387  			rp.resources <- wrapper
   388  			return nil, err
   389  		}
   390  	}
   391  
   392  	if rp.available.Add(-1) <= 0 {
   393  		rp.exhausted.Add(1)
   394  	}
   395  	rp.inUse.Add(1)
   396  	return wrapper.resource, err
   397  }
   398  
   399  // Put will return a resource to the pool. For every successful Get,
   400  // a corresponding Put is required. If you no longer need a resource,
   401  // you will need to call Put(nil) instead of returning the closed resource.
   402  // This will cause a new resource to be created in its place.
   403  func (rp *ResourcePool) Put(resource Resource) {
   404  	var wrapper resourceWrapper
   405  	var recreated bool
   406  	var hasSettings bool
   407  	if resource != nil {
   408  		wrapper = resourceWrapper{
   409  			resource: resource,
   410  			timeUsed: time.Now(),
   411  		}
   412  		hasSettings = resource.IsSettingApplied()
   413  		if resource.Expired(rp.extendedMaxLifetime()) {
   414  			rp.maxLifetimeClosed.Add(1)
   415  			resource.Close()
   416  			resource = nil
   417  		}
   418  	}
   419  	if resource == nil {
   420  		// Create new resource
   421  		rp.reopenResource(&wrapper)
   422  		recreated = true
   423  	}
   424  	if !hasSettings || recreated {
   425  		select {
   426  		case rp.resources <- wrapper:
   427  		default:
   428  			panic(errors.New("attempt to Put into a full ResourcePool"))
   429  		}
   430  	} else {
   431  		select {
   432  		case rp.settingResources <- wrapper:
   433  		default:
   434  			panic(errors.New("attempt to Put into a full ResourcePool"))
   435  		}
   436  	}
   437  	rp.inUse.Add(-1)
   438  	rp.available.Add(1)
   439  }
   440  
   441  func (rp *ResourcePool) reopenResource(wrapper *resourceWrapper) {
   442  	if r, err := rp.factory(context.TODO()); err == nil {
   443  		wrapper.resource = r
   444  		wrapper.timeUsed = time.Now()
   445  	} else {
   446  		wrapper.resource = nil
   447  		rp.active.Add(-1)
   448  	}
   449  }
   450  
   451  // SetCapacity changes the capacity of the pool.
   452  // You can use it to shrink or expand, but not beyond
   453  // the max capacity. If the change requires the pool
   454  // to be shrunk, SetCapacity waits till the necessary
   455  // number of resources are returned to the pool.
   456  // A SetCapacity of 0 is equivalent to closing the ResourcePool.
   457  func (rp *ResourcePool) SetCapacity(capacity int) error {
   458  	if capacity < 0 || capacity > cap(rp.resources) {
   459  		return fmt.Errorf("capacity %d is out of range", capacity)
   460  	}
   461  
   462  	// Atomically swap new capacity with old
   463  	var oldcap int
   464  	for {
   465  		oldcap = int(rp.capacity.Get())
   466  		if oldcap == 0 && capacity > 0 {
   467  			// Closed this before, re-open the channel
   468  			rp.resources = make(chan resourceWrapper, cap(rp.resources))
   469  			rp.settingResources = make(chan resourceWrapper, cap(rp.settingResources))
   470  		}
   471  		if oldcap == capacity {
   472  			return nil
   473  		}
   474  		if rp.capacity.CompareAndSwap(int64(oldcap), int64(capacity)) {
   475  			break
   476  		}
   477  	}
   478  
   479  	// If the required capacity is less than the current capacity,
   480  	// then we need to wait till the current resources are returned
   481  	// to the pool and close them from any of the channel.
   482  	// Otherwise, if the required capacity is more than the current capacity,
   483  	// then we just add empty resource to the channel.
   484  	if capacity < oldcap {
   485  		for i := 0; i < oldcap-capacity; i++ {
   486  			var wrapper resourceWrapper
   487  			select {
   488  			case wrapper = <-rp.resources:
   489  			case wrapper = <-rp.settingResources:
   490  			}
   491  			if wrapper.resource != nil {
   492  				wrapper.resource.Close()
   493  				rp.active.Add(-1)
   494  			}
   495  			rp.available.Add(-1)
   496  		}
   497  	} else {
   498  		for i := 0; i < capacity-oldcap; i++ {
   499  			rp.resources <- resourceWrapper{}
   500  			rp.available.Add(1)
   501  		}
   502  	}
   503  	if capacity == 0 {
   504  		close(rp.resources)
   505  		close(rp.settingResources)
   506  	}
   507  	return nil
   508  }
   509  
   510  func (rp *ResourcePool) recordWait(start time.Time) {
   511  	rp.waitCount.Add(1)
   512  	rp.waitTime.Add(time.Since(start))
   513  	if rp.logWait != nil {
   514  		rp.logWait(start)
   515  	}
   516  }
   517  
   518  // SetIdleTimeout sets the idle timeout. It can only be used if there was an
   519  // idle timeout set when the pool was created.
   520  func (rp *ResourcePool) SetIdleTimeout(idleTimeout time.Duration) {
   521  	if rp.idleTimer == nil {
   522  		panic("SetIdleTimeout called when timer not initialized")
   523  	}
   524  
   525  	rp.idleTimeout.Set(idleTimeout)
   526  	rp.idleTimer.SetInterval(idleTimeout / 10)
   527  }
   528  
   529  // StatsJSON returns the stats in JSON format.
   530  func (rp *ResourcePool) StatsJSON() string {
   531  	return fmt.Sprintf(`{"Capacity": %v, "Available": %v, "Active": %v, "InUse": %v, "MaxCapacity": %v, "WaitCount": %v, "WaitTime": %v, "IdleTimeout": %v, "IdleClosed": %v, "MaxLifetimeClosed": %v, "Exhausted": %v}`,
   532  		rp.Capacity(),
   533  		rp.Available(),
   534  		rp.Active(),
   535  		rp.InUse(),
   536  		rp.MaxCap(),
   537  		rp.WaitCount(),
   538  		rp.WaitTime().Nanoseconds(),
   539  		rp.IdleTimeout().Nanoseconds(),
   540  		rp.IdleClosed(),
   541  		rp.MaxLifetimeClosed(),
   542  		rp.Exhausted(),
   543  	)
   544  }
   545  
   546  // Capacity returns the capacity.
   547  func (rp *ResourcePool) Capacity() int64 {
   548  	return rp.capacity.Get()
   549  }
   550  
   551  // Available returns the number of currently unused and available resources.
   552  func (rp *ResourcePool) Available() int64 {
   553  	return rp.available.Get()
   554  }
   555  
   556  // Active returns the number of active (i.e. non-nil) resources either in the
   557  // pool or claimed for use
   558  func (rp *ResourcePool) Active() int64 {
   559  	return rp.active.Get()
   560  }
   561  
   562  // InUse returns the number of claimed resources from the pool
   563  func (rp *ResourcePool) InUse() int64 {
   564  	return rp.inUse.Get()
   565  }
   566  
   567  // MaxCap returns the max capacity.
   568  func (rp *ResourcePool) MaxCap() int64 {
   569  	return int64(cap(rp.resources))
   570  }
   571  
   572  // WaitCount returns the total number of waits.
   573  func (rp *ResourcePool) WaitCount() int64 {
   574  	return rp.waitCount.Get()
   575  }
   576  
   577  // WaitTime returns the total wait time.
   578  func (rp *ResourcePool) WaitTime() time.Duration {
   579  	return rp.waitTime.Get()
   580  }
   581  
   582  // IdleTimeout returns the resource idle timeout.
   583  func (rp *ResourcePool) IdleTimeout() time.Duration {
   584  	return rp.idleTimeout.Get()
   585  }
   586  
   587  // IdleClosed returns the count of resources closed due to idle timeout.
   588  func (rp *ResourcePool) IdleClosed() int64 {
   589  	return rp.idleClosed.Get()
   590  }
   591  
   592  // extendedLifetimeTimeout returns random duration within range [maxLifetime, 2*maxLifetime)
   593  func (rp *ResourcePool) extendedMaxLifetime() time.Duration {
   594  	maxLifetime := rp.maxLifetime.Get()
   595  	if maxLifetime == 0 {
   596  		return 0
   597  	}
   598  	return maxLifetime + time.Duration(rand.Int63n(maxLifetime.Nanoseconds()))
   599  }
   600  
   601  // MaxLifetimeClosed returns the count of resources closed due to refresh timeout.
   602  func (rp *ResourcePool) MaxLifetimeClosed() int64 {
   603  	return rp.maxLifetimeClosed.Get()
   604  }
   605  
   606  // Exhausted returns the number of times Available dropped below 1
   607  func (rp *ResourcePool) Exhausted() int64 {
   608  	return rp.exhausted.Get()
   609  }
   610  
   611  // GetCount returns the number of times get was called
   612  func (rp *ResourcePool) GetCount() int64 {
   613  	return rp.getCount.Get()
   614  }
   615  
   616  // GetSettingCount returns the number of times getWithSettings was called
   617  func (rp *ResourcePool) GetSettingCount() int64 {
   618  	return rp.getSettingCount.Get()
   619  }
   620  
   621  // DiffSettingCount returns the number of times different setting were applied on the resource.
   622  func (rp *ResourcePool) DiffSettingCount() int64 {
   623  	return rp.diffSettingCount.Get()
   624  }
   625  
   626  // ResetSettingCount returns the number of times setting were reset on the resource.
   627  func (rp *ResourcePool) ResetSettingCount() int64 {
   628  	return rp.resetSettingCount.Get()
   629  }