carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "errors"
6 "log"
7 "sort"
8 "time"
9
10 "github.com/carlosframework/platform/internal/hostbox"
11 "github.com/carlosframework/platform/internal/instancectl"
12)
13
14// Select at most one page of the oldest complete parks. Incomplete records and
15// unknown contents never contribute reclaimable space. The namespace lock
16// serializes selection, slot ownership and identity-checked deletion.
17func (l *servingParkLedger) maintainParks(ctx context.Context, room func() error) error {
18 if room == nil {
19 return hostbox.ErrUnsupported
20 }
21 return l.withNamespace(ctx, func() error {
22 if err := ctx.Err(); err != nil {
23 return err
24 }
25 if err := l.collectOrphanBindings(ctx); err != nil {
26 return err
27 }
28 pressure := room()
29 if pressure != nil && !errors.Is(pressure, instancectl.ErrDiskFull) {
30 return pressure
31 }
32 var candidates []servingParkRecord
33 var failures []error
34 after := ""
35 for {
36 ids, more, err := l.recoveryIDs(ctx, after, servingParkRecoveryLimit)
37 if err != nil {
38 return err
39 }
40 for _, id := range ids {
41 if err := ctx.Err(); err != nil {
42 return err
43 }
44 r, err := l.read(id)
45 if err != nil {
46 if len(failures) < servingParkRecoveryLimit {
47 failures = append(failures, err)
48 }
49 continue
50 }
51 if r.Complete && !r.Closed && r.Disposition == servingParkRetain && (pressure != nil || !l.now().Before(r.ReapAfter)) {
52 candidates = append(candidates, r)
53 sort.Slice(candidates, func(i, j int) bool {
54 if candidates[i].ParkedAt.Equal(candidates[j].ParkedAt) {
55 return candidates[i].Authorization.ID < candidates[j].Authorization.ID
56 }
57 return candidates[i].ParkedAt.Before(candidates[j].ParkedAt)
58 })
59 if len(candidates) > servingParkRecoveryLimit {
60 candidates = candidates[:servingParkRecoveryLimit]
61 }
62 }
63 }
64 if !more {
65 break
66 }
67 after = ids[len(ids)-1]
68 }
69 for _, r := range candidates {
70 if err := ctx.Err(); err != nil {
71 return err
72 }
73 cause := "expiry"
74 if l.now().Before(r.ReapAfter) {
75 err := room()
76 if err == nil {
77 continue
78 }
79 if !errors.Is(err, instancectl.ErrDiskFull) {
80 return err
81 }
82 cause = "pressure"
83 }
84 err := func() error {
85 unlock, err := l.state.Lock(ctx, "slot-"+servingParkKey(r.Authorization.Successor.Box, r.Authorization.Successor.Host)+".lock")
86 if err != nil {
87 return err
88 }
89 defer unlock()
90 unlock, err = l.state.Lock(ctx, "retirement-"+r.Authorization.ID+".lock")
91 if err != nil {
92 return err
93 }
94 defer unlock()
95 if r.Deleting {
96 cause = r.Cause
97 }
98 if err := l.deleteLocked(ctx, &r, cause); err != nil {
99 return err
100 }
101 slot, err := l.slot(r.Authorization)
102 if err != nil {
103 return err
104 }
105 return l.collectClosedLocked(ctx, &r, slot)
106 }()
107 if err != nil {
108 failures = append(failures, err)
109 }
110 }
111 return errors.Join(failures...)
112 })
113}
114
115func (p *servingParkRuntime) Start(ctx context.Context) {
116 if p == nil {
117 return
118 }
119 p.mu.Lock()
120 if p.started || p.closing {
121 p.mu.Unlock()
122 return
123 }
124 p.started = true
125 p.mu.Unlock()
126 call, done, err := p.begin(ctx)
127 if err != nil {
128 return
129 }
130 go func() {
131 defer done()
132 ticker := time.NewTicker(time.Minute)
133 defer ticker.Stop()
134 after := ""
135 lastLog := time.Time{}
136 for {
137 bounded, cancel := context.WithCancel(call)
138 if a, ok := p.authority.(interface {
139 maintainServingParks(context.Context, string) (string, error)
140 }); ok {
141 var err error
142 after, err = a.maintainServingParks(bounded, after)
143 if err != nil && call.Err() == nil && time.Since(lastLog) >= time.Hour {
144 lastLog = time.Now()
145 log.Print(err)
146 }
147 }
148 cancel()
149 select {
150 case <-call.Done():
151 return
152 case <-ticker.C:
153 }
154 }
155 }()
156}
157
158func maintainServingParkPass(ctx context.Context, after string, cleanup func(context.Context) error, recover func(context.Context, string, int) (servingParkRecoveryPage, error), budget time.Duration) (string, error) {
159 pressure, stopPressure := context.WithTimeout(ctx, budget)
160 pressureErr := cleanup(pressure)
161 stopPressure()
162 recovery, stopRecovery := context.WithTimeout(ctx, budget)
163 defer stopRecovery()
164 page, err := recover(recovery, after, servingParkRecoveryLimit)
165 for _, result := range page.Results {
166 err = errors.Join(err, result.Err)
167 }
168 next := ""
169 if page.More {
170 next = page.Next
171 }
172 return next, errors.Join(pressureErr, err)
173}
174