239 lines
5.4 KiB
Go
239 lines
5.4 KiB
Go
package proxy
|
|
|
|
import (
|
|
"errors"
|
|
"sync/atomic"
|
|
)
|
|
|
|
const counterMask = uint64(1<<32 - 1)
|
|
|
|
var (
|
|
ErrReservationCommitted = errors.New("reservation is already committed")
|
|
ErrReservationFinished = errors.New("reservation is already finished")
|
|
)
|
|
|
|
type Capacity struct {
|
|
max atomic.Uint32
|
|
counters atomic.Uint64
|
|
reservationEnabled atomic.Bool
|
|
reserving atomic.Int64
|
|
configuredObserver *activityObserver
|
|
observer atomic.Pointer[activityObserver]
|
|
}
|
|
|
|
type activityObserver struct{ notify func(bool) }
|
|
|
|
func NewCapacity(max int64) *Capacity {
|
|
return NewCapacityWithActivityObserver(max, nil)
|
|
}
|
|
|
|
// NewCapacityWithActivityObserver reports successful zero-to-nonzero and
|
|
// nonzero-to-zero transitions. The observer must tolerate concurrent calls.
|
|
func NewCapacityWithActivityObserver(max int64, observer func(nonzero bool)) *Capacity {
|
|
capacity := &Capacity{}
|
|
if max < 0 || max > int64(counterMask) {
|
|
max = 0
|
|
}
|
|
capacity.max.Store(uint32(max))
|
|
capacity.reservationEnabled.Store(true)
|
|
if observer != nil {
|
|
capacity.configuredObserver = &activityObserver{notify: observer}
|
|
capacity.observer.Store(capacity.configuredObserver)
|
|
}
|
|
return capacity
|
|
}
|
|
|
|
// SetActivityObservationEnabled lets snapshot ownership disable callbacks for
|
|
// current Proxies and enable them only while a runtime is retired and draining.
|
|
func (c *Capacity) SetActivityObservationEnabled(enabled bool) {
|
|
if c == nil || c.configuredObserver == nil {
|
|
return
|
|
}
|
|
if enabled {
|
|
c.observer.Store(c.configuredObserver)
|
|
return
|
|
}
|
|
c.observer.Store(nil)
|
|
}
|
|
|
|
// SetReservationEnabled gates new reservations without changing existing
|
|
// Active or Reserved counters. It is used while a snapshot retires a Proxy.
|
|
func (c *Capacity) SetReservationEnabled(enabled bool) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
c.reservationEnabled.Store(enabled)
|
|
}
|
|
|
|
// Reclaimable reports whether a retired capacity can be forgotten without
|
|
// losing an in-flight reservation or draining runtime counter.
|
|
func (c *Capacity) Reclaimable() bool {
|
|
if c == nil || c.reservationEnabled.Load() || c.reserving.Load() != 0 {
|
|
return false
|
|
}
|
|
active, reserved, _ := c.Counters()
|
|
return active == 0 && reserved == 0
|
|
}
|
|
|
|
func (c *Capacity) SetMax(max int64) bool {
|
|
if max < 0 || max > int64(counterMask) {
|
|
return false
|
|
}
|
|
c.max.Store(uint32(max))
|
|
return true
|
|
}
|
|
|
|
func (c *Capacity) Max() int64 { return int64(c.max.Load()) }
|
|
|
|
func (c *Capacity) Counters() (active, reserved, maximum int64) {
|
|
if c == nil {
|
|
return 0, 0, 0
|
|
}
|
|
packed := c.counters.Load()
|
|
activeCounter, reservedCounter := unpack(packed)
|
|
return int64(activeCounter), int64(reservedCounter), int64(c.max.Load())
|
|
}
|
|
|
|
func (c *Capacity) Reserve() (*Reservation, bool) {
|
|
if c == nil || !c.beginReservation() {
|
|
return nil, false
|
|
}
|
|
defer c.reserving.Add(-1)
|
|
for {
|
|
if !c.reservationEnabled.Load() {
|
|
return nil, false
|
|
}
|
|
current := c.counters.Load()
|
|
active, reserved := unpack(current)
|
|
if active+reserved >= c.max.Load() {
|
|
return nil, false
|
|
}
|
|
next := pack(active, reserved+1)
|
|
if c.counters.CompareAndSwap(current, next) {
|
|
if !c.reservationEnabled.Load() {
|
|
c.cancel()
|
|
return nil, false
|
|
}
|
|
if active+reserved == 0 {
|
|
if observer := c.observer.Load(); observer != nil {
|
|
observer.notify(true)
|
|
}
|
|
}
|
|
return &Reservation{capacity: c}, true
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Capacity) beginReservation() bool {
|
|
if !c.reservationEnabled.Load() {
|
|
return false
|
|
}
|
|
c.reserving.Add(1)
|
|
if c.reservationEnabled.Load() {
|
|
return true
|
|
}
|
|
c.reserving.Add(-1)
|
|
return false
|
|
}
|
|
|
|
func (c *Capacity) Active() int64 {
|
|
active, _ := unpack(c.counters.Load())
|
|
return int64(active)
|
|
}
|
|
|
|
func (c *Capacity) Reserved() int64 {
|
|
_, reserved := unpack(c.counters.Load())
|
|
return int64(reserved)
|
|
}
|
|
|
|
func (c *Capacity) commit() {
|
|
for {
|
|
current := c.counters.Load()
|
|
active, reserved := unpack(current)
|
|
if reserved == 0 {
|
|
return
|
|
}
|
|
if c.counters.CompareAndSwap(current, pack(active+1, reserved-1)) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Capacity) cancel() {
|
|
for {
|
|
current := c.counters.Load()
|
|
active, reserved := unpack(current)
|
|
if reserved == 0 {
|
|
return
|
|
}
|
|
next := pack(active, reserved-1)
|
|
if c.counters.CompareAndSwap(current, next) {
|
|
if active+reserved == 1 {
|
|
if observer := c.observer.Load(); observer != nil {
|
|
observer.notify(false)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Capacity) release() {
|
|
for {
|
|
current := c.counters.Load()
|
|
active, reserved := unpack(current)
|
|
if active == 0 {
|
|
return
|
|
}
|
|
next := pack(active-1, reserved)
|
|
if c.counters.CompareAndSwap(current, next) {
|
|
if active+reserved == 1 {
|
|
if observer := c.observer.Load(); observer != nil {
|
|
observer.notify(false)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func pack(active, reserved uint32) uint64 {
|
|
return uint64(reserved)<<32 | uint64(active)
|
|
}
|
|
|
|
func unpack(value uint64) (active, reserved uint32) {
|
|
return uint32(value & counterMask), uint32(value >> 32)
|
|
}
|
|
|
|
type Reservation struct {
|
|
capacity *Capacity
|
|
state atomic.Uint32
|
|
}
|
|
|
|
func (r *Reservation) Commit() error {
|
|
if !r.state.CompareAndSwap(0, 1) {
|
|
if r.state.Load() == 1 {
|
|
return ErrReservationCommitted
|
|
}
|
|
return ErrReservationFinished
|
|
}
|
|
r.capacity.commit()
|
|
return nil
|
|
}
|
|
|
|
func (r *Reservation) Cancel() error {
|
|
if !r.state.CompareAndSwap(0, 2) {
|
|
return ErrReservationFinished
|
|
}
|
|
r.capacity.cancel()
|
|
return nil
|
|
}
|
|
|
|
func (r *Reservation) Release() error {
|
|
if !r.state.CompareAndSwap(1, 2) {
|
|
return ErrReservationFinished
|
|
}
|
|
r.capacity.release()
|
|
return nil
|
|
}
|