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 }