github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/quotapool/intpool.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package quotapool
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"math"
    17  	"strconv"
    18  	"sync"
    19  	"sync/atomic"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    22  	"github.com/cockroachdb/errors"
    23  )
    24  
    25  // IntPool manages allocating integer units of quota to clients.
    26  // Clients may acquire quota in two ways, using Acquire which requires the
    27  // client to specify the quantity of quota at call time and AcquireFunc which
    28  // allows the client to provide a function which will be used to determine
    29  // whether a quantity of quota is sufficient when it becomes available.
    30  type IntPool struct {
    31  	qp *QuotaPool
    32  
    33  	// capacity maintains how much total quota there is (not necessarily available).
    34  	// Accessed atomically!
    35  	// The capacity is originally set when the IntPool is constructed, and then it
    36  	// can be decreased by IntAlloc.Freeze().
    37  	capacity uint64
    38  
    39  	// updateCapacityMu synchronizes accesses to the capacity.
    40  	updateCapacityMu syncutil.Mutex
    41  }
    42  
    43  // IntAlloc is an allocated quantity which should be released.
    44  type IntAlloc struct {
    45  	// alloc may be negative when used as the current quota for an IntPool or when
    46  	// lowering the capacity (see UpdateCapacity). Allocs acquired by clients of
    47  	// this package will never be negative.
    48  	alloc int64
    49  	p     *IntPool
    50  }
    51  
    52  // Release releases an IntAlloc back into the IntPool.
    53  // It is safe to release into a closed pool.
    54  func (ia *IntAlloc) Release() {
    55  	ia.p.qp.Add((*intAlloc)(ia))
    56  }
    57  
    58  // Acquired returns the quantity that this alloc has acquired.
    59  func (ia *IntAlloc) Acquired() uint64 {
    60  	return uint64(ia.alloc)
    61  }
    62  
    63  // Merge adds the acquired resources in other to ia. Other may not be used after
    64  // it has been merged. It is illegal to merge allocs from different pools and
    65  // doing so will result in a panic.
    66  func (ia *IntAlloc) Merge(other *IntAlloc) {
    67  	if ia.p != other.p {
    68  		panic("cannot merge IntAllocs from two different pools")
    69  	}
    70  	ia.alloc = min(int64(ia.p.Capacity()), ia.alloc+other.alloc)
    71  	ia.p.putIntAlloc(other)
    72  }
    73  
    74  // Freeze informs the quota pool that this allocation will never be Release()ed.
    75  // Releasing it later is illegal and will lead to panics.
    76  //
    77  // Using Freeze and UpdateCapacity on the same pool may require explicit
    78  // coordination. It is illegal to freeze allocated capacity which is no longer
    79  // available - specifically it is illegal to make the capacity of an IntPool
    80  // negative. Imagine the case where the capacity of an IntPool is initially 10.
    81  // An allocation of 10 is acquired. Then, while it is held, the pool's capacity
    82  // is updated to be 9. Then the outstanding allocation is frozen. This would
    83  // make the total capacity of the IntPool negative which is not allowed and will
    84  // lead to a panic. In general it's a bad idea to freeze allocated quota from a
    85  // pool which will ever have its capacity decreased.
    86  //
    87  // AcquireFunc() requests will be woken up with an updated Capacity, and Alloc()
    88  // requests will be trimmed accordingly.
    89  func (ia *IntAlloc) Freeze() {
    90  	ia.p.decCapacity(uint64(ia.alloc))
    91  	ia.p = nil // ensure that future uses of this alloc will panic
    92  }
    93  
    94  // String formats an IntAlloc as a string.
    95  func (ia *IntAlloc) String() string {
    96  	if ia == nil {
    97  		return strconv.Itoa(0)
    98  	}
    99  	return strconv.FormatInt(ia.alloc, 10)
   100  }
   101  
   102  // from returns true if this IntAlloc is from p.
   103  func (ia *IntAlloc) from(p *IntPool) bool {
   104  	return ia.p == p
   105  }
   106  
   107  // intAlloc is used to make IntAlloc implement Resource without muddling its
   108  // exported interface.
   109  type intAlloc IntAlloc
   110  
   111  // Merge makes intAlloc a Resource.
   112  func (ia *intAlloc) Merge(other Resource) {
   113  	(*IntAlloc)(ia).Merge((*IntAlloc)(other.(*intAlloc)))
   114  }
   115  
   116  // NewIntPool creates a new named IntPool.
   117  //
   118  // capacity is the amount of quota initially available. The maximum capacity
   119  // is math.MaxInt64. If the capacity argument exceeds that value, this function
   120  // will panic.
   121  func NewIntPool(name string, capacity uint64, options ...Option) *IntPool {
   122  	assertCapacityIsValid(capacity)
   123  	p := IntPool{
   124  		capacity: capacity,
   125  	}
   126  	p.qp = New(name, (*intAlloc)(p.newIntAlloc(int64(capacity))), options...)
   127  	return &p
   128  }
   129  
   130  // Acquire acquires the specified amount of quota from the pool. On success, a
   131  // non-nil alloc is returned and Release() must be called on it to return the
   132  // quota to the pool.
   133  //
   134  // If 'v' is greater than the total capacity of the pool, we instead try to
   135  // acquire quota equal to the maximum capacity. If the maximum capacity is
   136  // decreased while this request is ongoing, the request is again truncated to
   137  // the maximum capacity.
   138  //
   139  // Acquisitions of 0 return immediately with no error, even if the IntPool is
   140  // closed.
   141  //
   142  // Acquisitions of more than 0 from a pool with 0 capacity always returns an
   143  // ErrNotEnoughQuota.
   144  //
   145  // Safe for concurrent use.
   146  func (p *IntPool) Acquire(ctx context.Context, v uint64) (*IntAlloc, error) {
   147  	return p.acquireMaybeWait(ctx, v, true /* wait */)
   148  }
   149  
   150  // TryAcquire is like Acquire but if there is insufficient quota to acquire
   151  // immediately the method will return ErrNotEnoughQuota.
   152  func (p *IntPool) TryAcquire(ctx context.Context, v uint64) (*IntAlloc, error) {
   153  	return p.acquireMaybeWait(ctx, v, false /* wait */)
   154  }
   155  
   156  func (p *IntPool) acquireMaybeWait(ctx context.Context, v uint64, wait bool) (*IntAlloc, error) {
   157  	// Special case acquisitions of size 0.
   158  	if v == 0 {
   159  		return p.newIntAlloc(0), nil
   160  	}
   161  	// Special case capacity of 0.
   162  	if p.Capacity() == 0 {
   163  		return nil, ErrNotEnoughQuota
   164  	}
   165  	// The maximum capacity is math.MaxInt64 so we can always truncate requests
   166  	// to that value.
   167  	if v > math.MaxInt64 {
   168  		v = math.MaxInt64
   169  	}
   170  	r := p.newIntRequest(v)
   171  	defer p.putIntRequest(r)
   172  	var req Request
   173  	if wait {
   174  		req = r
   175  	} else {
   176  		req = (*intRequestNoWait)(r)
   177  	}
   178  	if err := p.qp.Acquire(ctx, req); err != nil {
   179  		return nil, err
   180  	}
   181  	return p.newIntAlloc(int64(r.want)), nil
   182  }
   183  
   184  // Release will release allocs back to their pool. Allocs which are from p are
   185  // merged into a single alloc before being added to avoid synchronizing
   186  // on p multiple times. Allocs which did not come from p are released
   187  // one at a time. It is legal to pass nil values in allocs.
   188  func (p *IntPool) Release(allocs ...*IntAlloc) {
   189  	var toRelease *IntAlloc
   190  	for _, alloc := range allocs {
   191  		switch {
   192  		case alloc == nil:
   193  			continue
   194  		case !alloc.from(p):
   195  			// If alloc is not from p, call Release() on it directly.
   196  			alloc.Release()
   197  			continue
   198  		case toRelease == nil:
   199  			toRelease = alloc
   200  		default:
   201  			toRelease.Merge(alloc)
   202  		}
   203  	}
   204  	if toRelease != nil {
   205  		toRelease.Release()
   206  	}
   207  }
   208  
   209  // IntRequestFunc is used to request a quantity of quota determined when quota is
   210  // available rather than before requesting.
   211  //
   212  // If the request is satisfied, the function returns the amount of quota
   213  // consumed and no error. If the request is not satisfied because there's no
   214  // enough quota currently available, ErrNotEnoughQuota is returned to cause the
   215  // function to be called again where more quota becomes available. took has to
   216  // be 0 (i.e. it is not allowed for the request to save some quota for later
   217  // use). If any other error is returned, took again has to be 0. The function
   218  // will not be called any more and the error will be returned from
   219  // IntPool.AcquireFunc().
   220  type IntRequestFunc func(ctx context.Context, p PoolInfo) (took uint64, err error)
   221  
   222  // ErrNotEnoughQuota is returned by IntRequestFuncs when they want to be called
   223  // again once there's new resources.
   224  var ErrNotEnoughQuota = fmt.Errorf("not enough quota available")
   225  
   226  // HasErrClosed returns true if this error is or contains an ErrClosed error.
   227  func HasErrClosed(err error) bool {
   228  	return errors.HasType(err, (*ErrClosed)(nil))
   229  }
   230  
   231  // PoolInfo represents the information that the IntRequestFunc gets about the current quota pool conditions.
   232  type PoolInfo struct {
   233  	// Available is the amount of quota available to be consumed. This is the
   234  	// maximum value that the `took` return value from IntRequestFunc can be set
   235  	// to.
   236  	// Note that Available() can be 0. This happens when the IntRequestFunc() is
   237  	// called as a result of the pool's capacity decreasing.
   238  	Available uint64
   239  
   240  	// Capacity returns the maximum capacity available in the pool. This can
   241  	// decrease over time. It can be used to determine that the resources required
   242  	// by a request will never be available.
   243  	Capacity uint64
   244  }
   245  
   246  // AcquireFunc acquires a quantity of quota determined by a function which is
   247  // called with a quantity of available quota.
   248  func (p *IntPool) AcquireFunc(ctx context.Context, f IntRequestFunc) (*IntAlloc, error) {
   249  	return p.acquireFuncMaybeWait(ctx, f, true /* wait */)
   250  }
   251  
   252  // TryAcquireFunc is like AcquireFunc but if insufficient quota exists the
   253  // method will return ErrNotEnoughQuota rather than waiting for quota to become
   254  // available.
   255  func (p *IntPool) TryAcquireFunc(ctx context.Context, f IntRequestFunc) (*IntAlloc, error) {
   256  	return p.acquireFuncMaybeWait(ctx, f, false /* wait */)
   257  }
   258  
   259  func (p *IntPool) acquireFuncMaybeWait(
   260  	ctx context.Context, f IntRequestFunc, wait bool,
   261  ) (*IntAlloc, error) {
   262  	r := p.newIntFuncRequest(f)
   263  	defer p.putIntFuncRequest(r)
   264  	var req Request
   265  	if wait {
   266  		req = r
   267  	} else {
   268  		req = (*intFuncRequestNoWait)(r)
   269  	}
   270  	err := p.qp.Acquire(ctx, req)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  	if r.err != nil {
   275  		if r.took != 0 {
   276  			panic(fmt.Sprintf("both took set (%d) and err (%s)", r.took, r.err))
   277  		}
   278  		return nil, r.err
   279  	}
   280  	// NB: We know that r.took must be less than math.MaxInt64 because capacity
   281  	// cannot exceed that value and took cannot exceed capacity.
   282  	return p.newIntAlloc(int64(r.took)), nil
   283  }
   284  
   285  // Len returns the current length of the queue for this IntPool.
   286  func (p *IntPool) Len() int {
   287  	return p.qp.Len()
   288  }
   289  
   290  // ApproximateQuota will report approximately the amount of quota available in
   291  // the pool. It's "approximate" because, if there's an acquisition in progress,
   292  // this might return an "intermediate" value - one that does not fully reflect
   293  // the capacity either before that acquisitions started or after it will have
   294  // finished.
   295  func (p *IntPool) ApproximateQuota() (q uint64) {
   296  	p.qp.ApproximateQuota(func(r Resource) {
   297  		if ia, ok := r.(*intAlloc); ok {
   298  			q = uint64(max(0, ia.alloc))
   299  		}
   300  	})
   301  	return q
   302  }
   303  
   304  // Full returns true if no quota is outstanding.
   305  func (p *IntPool) Full() bool {
   306  	return p.ApproximateQuota() == p.Capacity()
   307  }
   308  
   309  // Close signals to all ongoing and subsequent acquisitions that the pool is
   310  // closed and that an error should be returned.
   311  //
   312  // Safe for concurrent use.
   313  func (p *IntPool) Close(reason string) {
   314  	p.qp.Close(reason)
   315  }
   316  
   317  // IntPoolCloser implements stop.Closer.
   318  type IntPoolCloser struct {
   319  	reason string
   320  	p      *IntPool
   321  }
   322  
   323  // Close makes the IntPoolCloser a stop.Closer.
   324  func (ipc IntPoolCloser) Close() {
   325  	ipc.p.Close(ipc.reason)
   326  }
   327  
   328  // Closer returns a struct which implements stop.Closer.
   329  func (p *IntPool) Closer(reason string) IntPoolCloser {
   330  	return IntPoolCloser{p: p, reason: reason}
   331  }
   332  
   333  var intAllocSyncPool = sync.Pool{
   334  	New: func() interface{} { return new(IntAlloc) },
   335  }
   336  
   337  func (p *IntPool) newIntAlloc(v int64) *IntAlloc {
   338  	ia := intAllocSyncPool.Get().(*IntAlloc)
   339  	*ia = IntAlloc{p: p, alloc: v}
   340  	return ia
   341  }
   342  
   343  func (p *IntPool) putIntAlloc(ia *IntAlloc) {
   344  	*ia = IntAlloc{}
   345  	intAllocSyncPool.Put(ia)
   346  }
   347  
   348  var intRequestSyncPool = sync.Pool{
   349  	New: func() interface{} { return new(intRequest) },
   350  }
   351  
   352  // newIntRequest allocates an intRequest from the sync.Pool.
   353  // It should be returned with putIntRequest.
   354  func (p *IntPool) newIntRequest(v uint64) *intRequest {
   355  	r := intRequestSyncPool.Get().(*intRequest)
   356  	*r = intRequest{want: v}
   357  	return r
   358  }
   359  
   360  func (p *IntPool) putIntRequest(r *intRequest) {
   361  	*r = intRequest{}
   362  	intRequestSyncPool.Put(r)
   363  }
   364  
   365  var intFuncRequestSyncPool = sync.Pool{
   366  	New: func() interface{} { return new(intFuncRequest) },
   367  }
   368  
   369  // newIntRequest allocates an intFuncRequest from the sync.Pool.
   370  // It should be returned with putIntFuncRequest.
   371  func (p *IntPool) newIntFuncRequest(f IntRequestFunc) *intFuncRequest {
   372  	r := intFuncRequestSyncPool.Get().(*intFuncRequest)
   373  	*r = intFuncRequest{f: f, p: p}
   374  	return r
   375  }
   376  
   377  func (p *IntPool) putIntFuncRequest(r *intFuncRequest) {
   378  	*r = intFuncRequest{}
   379  	intFuncRequestSyncPool.Put(r)
   380  }
   381  
   382  // Capacity returns the amount of quota managed by this pool.
   383  func (p *IntPool) Capacity() uint64 {
   384  	return atomic.LoadUint64(&p.capacity)
   385  }
   386  
   387  // UpdateCapacity sets the capacity to newCapacity. If the current capacity
   388  // is higher than the new capacity, currently running requests will not be
   389  // affected. When the capacity is increased, new quota will be added. The total
   390  // quantity of outstanding quota will never exceed the maximum value of the
   391  // capacity which existed when any outstanding quota was acquired.
   392  func (p *IntPool) UpdateCapacity(newCapacity uint64) {
   393  	assertCapacityIsValid(newCapacity)
   394  
   395  	// Synchronize updates so that we never lose any quota. If we did not
   396  	// synchronize then a rapid succession of increases and decreases could have
   397  	// their associated updates to the current quota re-ordered.
   398  	//
   399  	// Imagine the capacity moves through:
   400  	//   100, 1, 100, 1, 100
   401  	// It would lead to the following intAllocs being released:
   402  	//   -99, 99, -99, 99
   403  	// If was reordered to:
   404  	//   99, 99, -99, -99
   405  	// Then we'd effectively ignore the additions at the beginning because they
   406  	// would push the alloc above the capacity and then we'd make the
   407  	// corresponding subtractions, leading to a final state of -98 even though
   408  	// we'd like it to be 1.
   409  	p.updateCapacityMu.Lock()
   410  	defer p.updateCapacityMu.Unlock()
   411  
   412  	// NB: if we're going to be lowering the capacity, we need to remove the
   413  	// quota from the pool before we update the capacity value. Imagine the case
   414  	// where there's a concurrent release of quota back into the pool. If it sees
   415  	// the newer capacity but the old quota value, it would push the value above
   416  	// the new capacity and thus get truncated. This is a bummer. We prevent this
   417  	// by subtracting from the quota pool (perhaps pushing it into negative
   418  	// territory), before we change the capacity.
   419  	oldCapacity := atomic.LoadUint64(&p.capacity)
   420  	delta := int64(newCapacity) - int64(oldCapacity)
   421  	if delta < 0 {
   422  		p.newIntAlloc(delta).Release()
   423  		delta = 0 // we still want to release after the update to wake goroutines
   424  	}
   425  	atomic.SwapUint64(&p.capacity, newCapacity)
   426  	p.newIntAlloc(delta).Release()
   427  }
   428  
   429  // decCapacity decrements the capacity by c.
   430  func (p *IntPool) decCapacity(c uint64) {
   431  	p.updateCapacityMu.Lock()
   432  	defer p.updateCapacityMu.Unlock()
   433  
   434  	oldCapacity := p.Capacity()
   435  	if int64(oldCapacity)-int64(c) < 0 {
   436  		panic("cannot freeze quota which is no longer part of the pool")
   437  	}
   438  	newCapacity := oldCapacity - c
   439  	atomic.SwapUint64(&p.capacity, newCapacity)
   440  
   441  	// Wake up the request at the front of the queue. The decrement above may race
   442  	// with an ongoing request (which is why it's an atomic access), but in any
   443  	// case that request is evaluated again.
   444  	p.newIntAlloc(0).Release()
   445  }
   446  
   447  // intRequest is used to acquire a quantity from the quota known ahead of time.
   448  type intRequest struct {
   449  	want uint64
   450  }
   451  
   452  func (r *intRequest) Acquire(ctx context.Context, v Resource) (fulfilled bool, extra Resource) {
   453  	ia := v.(*intAlloc)
   454  	want := min(int64(r.want), int64(ia.p.Capacity()))
   455  	if ia.alloc < want {
   456  		return false, nil
   457  	}
   458  	r.want = uint64(want)
   459  	ia.alloc -= want
   460  	return true, ia
   461  }
   462  
   463  func (r *intRequest) ShouldWait() bool { return true }
   464  
   465  type intRequestNoWait intRequest
   466  
   467  func (r *intRequestNoWait) Acquire(
   468  	ctx context.Context, v Resource,
   469  ) (fulfilled bool, extra Resource) {
   470  	return (*intRequest)(r).Acquire(ctx, v)
   471  }
   472  
   473  func (r *intRequestNoWait) ShouldWait() bool { return false }
   474  
   475  // intFuncRequest is used to acquire a quantity from the pool which is not
   476  // known ahead of time.
   477  type intFuncRequest struct {
   478  	p    *IntPool
   479  	f    IntRequestFunc
   480  	took uint64
   481  	// err saves the error returned by r.f, if other than ErrNotEnoughQuota.
   482  	err error
   483  }
   484  
   485  func (r *intFuncRequest) Acquire(ctx context.Context, v Resource) (fulfilled bool, extra Resource) {
   486  	ia := v.(*intAlloc)
   487  	pi := PoolInfo{
   488  		Available: uint64(max(0, ia.alloc)),
   489  		Capacity:  ia.p.Capacity(),
   490  	}
   491  	took, err := r.f(ctx, pi)
   492  	if err != nil {
   493  		if took != 0 {
   494  			panic(fmt.Sprintf("IntRequestFunc returned both took: %d and err: %s", took, err))
   495  		}
   496  		if errors.Is(err, ErrNotEnoughQuota) {
   497  			return false, nil
   498  		}
   499  		r.err = err
   500  		// Take the request out of the queue and put all the quota back.
   501  		return true, ia
   502  	}
   503  	if took > math.MaxInt64 || int64(took) > ia.alloc {
   504  		panic(errors.Errorf("took %d quota > %d allocated", took, ia.alloc))
   505  	}
   506  	r.took = took
   507  	ia.alloc -= int64(took)
   508  	return true, ia
   509  }
   510  
   511  func min(a, b int64) (v int64) {
   512  	if a < b {
   513  		return a
   514  	}
   515  	return b
   516  }
   517  
   518  func max(a, b int64) (v int64) {
   519  	if a > b {
   520  		return a
   521  	}
   522  	return b
   523  }
   524  
   525  func (r *intFuncRequest) ShouldWait() bool { return true }
   526  
   527  type intFuncRequestNoWait intFuncRequest
   528  
   529  func (r *intFuncRequestNoWait) Acquire(
   530  	ctx context.Context, v Resource,
   531  ) (fulfilled bool, extra Resource) {
   532  	return (*intFuncRequest)(r).Acquire(ctx, v)
   533  }
   534  
   535  func (r *intFuncRequestNoWait) ShouldWait() bool { return false }
   536  
   537  // assertCapacityIsValid panics if capacity exceeds math.MaxInt64.
   538  func assertCapacityIsValid(capacity uint64) {
   539  	if capacity > math.MaxInt64 {
   540  		panic(errors.Errorf("capacity %d exceeds max capacity %d", capacity, math.MaxInt64))
   541  	}
   542  }