proxy-pool/internal/controller/pool/reconciler.go

65 lines
1.7 KiB
Go

package pool
import (
"errors"
"time"
"github.com/proxy-pool/proxy-pool/internal/domain/upstream"
)
var ErrInvalidReconcilePolicy = errors.New("invalid pool reconcile policy")
type ReconcilePolicy struct {
MinimumAvailableSlots int64
ExpectedPerFetch int
SafetyMargin time.Duration
}
type FetchNotifier interface {
Notify()
}
type ReconcileDecision struct {
AvailableSlots int64
PendingExpected int
FetchedTotal int64
FetchAllowance int
Triggered bool
}
type Reconciler struct {
policy ReconcilePolicy
budget *FetchBudget
notifier FetchNotifier
}
func NewReconciler(policy ReconcilePolicy, budget *FetchBudget, notifier FetchNotifier) (*Reconciler, error) {
if policy.MinimumAvailableSlots <= 0 || policy.ExpectedPerFetch <= 0 ||
policy.SafetyMargin < 0 || budget == nil || notifier == nil {
return nil, ErrInvalidReconcilePolicy
}
if budget.expected != policy.ExpectedPerFetch {
return nil, ErrInvalidReconcilePolicy
}
return &Reconciler{policy: policy, budget: budget, notifier: notifier}, nil
}
// Reconcile centralizes the cold-path decision. The notifier may coalesce many
// calls; Provider Reconciler atomically reserves the budget before doing I/O.
func (r *Reconciler) Reconcile(now time.Time, inventory upstream.Inventory) ReconcileDecision {
usage := r.budget.Snapshot()
decision := ReconcileDecision{
AvailableSlots: inventory.AvailableSlots(now, r.policy.SafetyMargin),
PendingExpected: usage.PendingExpected,
FetchedTotal: usage.FetchedTotal,
FetchAllowance: r.budget.FetchAllowance(),
}
if decision.AvailableSlots >= r.policy.MinimumAvailableSlots ||
decision.FetchAllowance < r.policy.ExpectedPerFetch {
return decision
}
r.notifier.Notify()
decision.Triggered = true
return decision
}