carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// The plans pass (plans-caps-packs spec §7, plans 2): learn whether plans are
2// on for this deployment and, only if they are, read the limits record of
3// every account this box serves into a last-known-good cache the activator
4// asks. Rides the same passes as telemetryPass.
5//
6// Self-hosters are never interrupted: with no marker (no catalog on the
7// console) the pass reads one object and applies nothing, a box with
8// CARLOS_PLAN_LIMITS=off reads nothing at all, and an account with no record
9// runs exactly as it always has.
10package host
11
12import (
13 "context"
14 "log"
15 "os"
16 "strings"
17 "sync"
18 "sync/atomic"
19 "time"
20
21 "github.com/carlosframework/platform/internal/plans"
22 "github.com/carlosframework/platform/internal/registry"
23 "github.com/carlosframework/platform/internal/release"
24)
25
26// planLimitsSnapshot is immutable once stored; nil in planLimits means plans
27// are off on this box.
28type planLimitsSnapshot struct {
29 byAccount map[string]plans.Limits
30}
31
32var planLimits atomic.Pointer[planLimitsSnapshot]
33
34// plansPassMu serialises whole passes: the full pass and the five-minute pass
35// both run plansPass, and a stalled pass must not publish over a newer one
36// (Astra plans-2 finding 2). Readers never take it.
37var plansPassMu sync.Mutex
38
39// planLimitsFailures counts consecutive failing passes for log throttling.
40var planLimitsFailures atomic.Int64
41
42// planLimitsSwitchOn reads CARLOS_PLAN_LIMITS: off, false, 0 or no turn plans
43// off on this box; anything else, including unset, leaves them to the marker.
44func planLimitsSwitchOn(getenv func(string) string) bool {
45 switch strings.ToLower(strings.TrimSpace(getenv("CARLOS_PLAN_LIMITS"))) {
46 case "off", "false", "0", "no":
47 return false
48 }
49 return true
50}
51
52// planLimitsFor is sqid's limits, or false when plans are off or the account
53// has no record.
54func planLimitsFor(sqid string) (plans.Limits, bool) {
55 snap := planLimits.Load()
56 if snap == nil {
57 return plans.Limits{}, false
58 }
59 l, ok := snap.byAccount[sqid]
60 return l, ok
61}
62
63func plansPass(ctx context.Context, s release.Store, reg *registry.Registry, getenv func(string) string) {
64 plansPassMu.Lock()
65 defer plansPassMu.Unlock()
66 if getenv == nil {
67 getenv = os.Getenv
68 }
69 if !planLimitsSwitchOn(getenv) {
70 planLimits.Store(nil)
71 return
72 }
73 marker, _, err := plans.GetMarker(ctx, s)
74 if err != nil {
75 planLimitsFailed("marker: %v", err)
76 return // keep the last good cache
77 }
78 if marker == nil || !marker.Enabled {
79 planLimits.Store(nil)
80 planLimitsFailures.Store(0)
81 return
82 }
83 routes, err := reg.List()
84 if err != nil {
85 planLimitsFailed("routes: %v", err)
86 return
87 }
88 prev := planLimits.Load()
89 next := &planLimitsSnapshot{byAccount: map[string]plans.Limits{}}
90 seen := map[string]bool{}
91 failed := false
92 for _, rt := range routes {
93 if rt.Kind == "status" {
94 continue
95 }
96 sqid := release.RouteAccount(rt)
97 if sqid == "" || seen[sqid] {
98 continue
99 }
100 seen[sqid] = true
101 rec, _, err := plans.GetLimits(ctx, s, sqid)
102 switch {
103 case err != nil:
104 failed = true
105 planLimitsFailed("%s: %v", sqid, err)
106 if prev != nil {
107 if l, ok := prev.byAccount[sqid]; ok {
108 next.byAccount[sqid] = l
109 }
110 }
111 case rec != nil:
112 next.byAccount[sqid] = rec.Limits
113 }
114 }
115 planLimits.Store(next)
116 if !failed {
117 planLimitsFailures.Store(0)
118 }
119}
120
121// planLimitsFailed logs the first failure of a run and every tenth after it.
122func planLimitsFailed(format string, args ...any) {
123 if n := planLimitsFailures.Add(1); n == 1 || n%10 == 0 {
124 log.Printf("agent: plans pass: "+format+" (keeping the last good limits)", args...)
125 }
126}
127
128// planIdleFor is the activator's plans idle hook: the route's account's idle
129// window from its limits, or 0 (no opinion) when plans are off or the account
130// has no record.
131func planIdleFor(rt registry.Route) time.Duration {
132 l, ok := planLimitsFor(release.RouteAccount(rt))
133 if !ok || l.IdleSeconds <= 0 {
134 return 0
135 }
136 return time.Duration(l.IdleSeconds) * time.Second
137}
138