65 lines
1.7 KiB
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
|
|
}
|