mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
50 lines
1.6 KiB
Go
50 lines
1.6 KiB
Go
package allocator
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// ErrAllocationBudgetExceeded is deliberately generic: callers should not
|
|
// learn quota internals, and the allocator can safely retry the leased match
|
|
// after the current window expires.
|
|
var ErrAllocationBudgetExceeded = fmt.Errorf("allocation budget exceeded")
|
|
|
|
// FixedWindowBudget is a process-local denial-of-wallet guard. It limits the
|
|
// number of provider allocation attempts per region in a time window. The
|
|
// production deployment must use the same policy behind a shared durable
|
|
// counter for a global quota; this type prevents one allocator replica from
|
|
// spending without bound and is useful in tests and single-replica setups.
|
|
type FixedWindowBudget struct {
|
|
mu sync.Mutex
|
|
limit int
|
|
window time.Duration
|
|
windowStart time.Time
|
|
counts map[string]int
|
|
}
|
|
|
|
func NewFixedWindowBudget(limit int, window time.Duration) (*FixedWindowBudget, error) {
|
|
if limit < 1 || window <= 0 {
|
|
return nil, fmt.Errorf("invalid allocation budget")
|
|
}
|
|
return &FixedWindowBudget{limit: limit, window: window, counts: make(map[string]int)}, nil
|
|
}
|
|
|
|
func (b *FixedWindowBudget) Allow(region string, now time.Time) error {
|
|
if b == nil || (region != "EU" && region != "NA") || now.IsZero() {
|
|
return fmt.Errorf("invalid allocation budget request")
|
|
}
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
if b.windowStart.IsZero() || !now.Before(b.windowStart.Add(b.window)) {
|
|
b.windowStart = now
|
|
b.counts = make(map[string]int)
|
|
}
|
|
if b.counts[region] >= b.limit {
|
|
return ErrAllocationBudgetExceeded
|
|
}
|
|
b.counts[region]++
|
|
return nil
|
|
}
|