carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package lease
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "io"
8 "log/slog"
9 "path"
10 "strings"
11 "time"
12
13 "github.com/benbjohnson/litestream"
14 "github.com/superfly/ltx"
15)
16
17var ErrGenerationRetention = errors.New("lease: generation retention requires exact versions through park")
18
19var ErrGenerationBinding = errors.New("lease: invalid or changed generation binding")
20
21const generationRequestLimit = 120 * time.Second
22
23// BindGeneration fixes all replica data requests below the held lease's
24// location. The authority callback is captured once and checked per request.
25func (h *Held) BindGeneration(prefix string, check func(context.Context) error) error {
26 if h == nil || h.leaser == nil || check == nil {
27 return ErrGenerationBinding
28 }
29 base := strings.TrimSuffix(path.Clean(h.leaser.Path), "/")
30 clean := path.Clean(prefix)
31 rel, ok := strings.CutPrefix(clean, base+"/")
32 if (prefix != clean && prefix != clean+"/") || strings.Contains(prefix, "\\") || base == "." || base == "" || !ok || strings.Contains(rel, "/") || !strings.HasPrefix(rel, "g-") || len(rel) == 2 {
33 return ErrGenerationBinding
34 }
35 h.mu.Lock()
36 defer h.mu.Unlock()
37 if h.busy || h.spent || h.generationPrefix != "" {
38 return ErrGenerationBinding
39 }
40 client := h.replicaClient
41 if client == nil {
42 var err error
43 client, err = newReplicaClient(h.leaser.Bucket, clean, h.region)
44 if err != nil {
45 return err
46 }
47 }
48 h.replicaClient = &generationClient{client: client, check: check}
49 h.generationPrefix = clean
50 return nil
51}
52
53// BindRoot is BindGeneration for the root layout (hostclaim.RootGeneration):
54// the replica data sits directly below the held lease's location, so no
55// prefix changes, but every replica request still first passes check, the
56// authority read before each one (moves §5.3 step 5, ruling G3; Task 5 fix
57// round 1, ruling 2). Without it a root-generation checkpoint would check
58// its authority once and then run every request unchecked. As immutable as
59// BindGeneration, and exclusive with it. Retention stays off under it, as
60// under a bound generation: the checking client refuses DeleteLTXFiles.
61func (h *Held) BindRoot(check func(context.Context) error) error {
62 if h == nil || h.leaser == nil || check == nil {
63 return ErrGenerationBinding
64 }
65 base := strings.TrimSuffix(path.Clean(h.leaser.Path), "/")
66 if base == "." || base == "" {
67 return ErrGenerationBinding
68 }
69 h.mu.Lock()
70 defer h.mu.Unlock()
71 if h.busy || h.spent || h.generationPrefix != "" {
72 return ErrGenerationBinding
73 }
74 client := h.replicaClient
75 if client == nil {
76 var err error
77 client, err = newReplicaClient(h.leaser.Bucket, base, h.region)
78 if err != nil {
79 return err
80 }
81 }
82 h.replicaClient = &generationClient{client: client, check: check}
83 // The lease's own path marks the binding, so neither BindGeneration nor
84 // a second BindRoot can change the client under a running checkpoint,
85 // and newStore keeps retention off.
86 h.generationPrefix = base
87 return nil
88}
89
90type generationClient struct {
91 client litestream.ReplicaClient
92 check func(context.Context) error
93}
94
95func (c *generationClient) Type() string { return c.client.Type() }
96func (c *generationClient) SetLogger(l *slog.Logger) { c.client.SetLogger(l) }
97func generationLevel(level int) error {
98 if level < 0 || level > litestream.SnapshotLevel {
99 return ErrGenerationBinding
100 }
101 return nil
102}
103func generationCoords(level int, min, max ltx.TXID) error {
104 if generationLevel(level) != nil || min == 0 || max == 0 || min > max {
105 return ErrGenerationBinding
106 }
107 return nil
108}
109func (c *generationClient) bounded(ctx context.Context) (context.Context, context.CancelFunc, error) {
110 if ctx == nil {
111 return nil, nil, ErrGenerationBinding
112 }
113 ctx, cancel := context.WithTimeout(ctx, generationRequestLimit)
114 if err := c.check(ctx); err != nil {
115 cancel()
116 return nil, nil, err
117 }
118 return ctx, cancel, nil
119}
120func (c *generationClient) Init(ctx context.Context) error {
121 ctx, cancel, err := c.bounded(ctx)
122 if err != nil {
123 return err
124 }
125 defer cancel()
126 return c.client.Init(ctx)
127}
128func (c *generationClient) LTXFiles(ctx context.Context, level int, seek ltx.TXID, metadata bool) (ltx.FileIterator, error) {
129 if err := generationLevel(level); err != nil {
130 return nil, err
131 }
132 ctx, cancel, err := c.bounded(ctx)
133 if err != nil {
134 return nil, err
135 }
136 it, err := c.client.LTXFiles(ctx, level, seek, metadata)
137 if err != nil {
138 cancel()
139 return nil, err
140 }
141 return &generationIterator{FileIterator: it, cancel: cancel}, nil
142}
143func (c *generationClient) OpenLTXFile(ctx context.Context, level int, min, max ltx.TXID, offset, size int64) (io.ReadCloser, error) {
144 if err := generationCoords(level, min, max); err != nil || offset < 0 || size < 0 {
145 return nil, ErrGenerationBinding
146 }
147 ctx, cancel, err := c.bounded(ctx)
148 if err != nil {
149 return nil, err
150 }
151 rc, err := c.client.OpenLTXFile(ctx, level, min, max, offset, size)
152 if err != nil {
153 cancel()
154 return nil, err
155 }
156 return &generationReader{ReadCloser: rc, cancel: cancel}, nil
157}
158func (c *generationClient) WriteLTXFile(ctx context.Context, level int, min, max ltx.TXID, r io.Reader) (*ltx.FileInfo, error) {
159 if pipe, ok := r.(*io.PipeReader); ok {
160 defer pipe.Close()
161 }
162 if err := generationCoords(level, min, max); err != nil || r == nil {
163 return nil, ErrGenerationBinding
164 }
165 ctx, cancel, err := c.bounded(ctx)
166 if err != nil {
167 return nil, err
168 }
169 defer cancel()
170 return c.client.WriteLTXFile(ctx, level, min, max, r)
171}
172func (c *generationClient) DeleteLTXFiles(ctx context.Context, files []*ltx.FileInfo) error {
173 if len(files) == 0 {
174 return ErrGenerationBinding
175 }
176 for _, f := range files {
177 if f == nil || generationCoords(f.Level, f.MinTXID, f.MaxTXID) != nil {
178 return ErrGenerationBinding
179 }
180 }
181 // FileInfo has no VersionID. Only the park engine may retire exact versions.
182 return ErrGenerationRetention
183}
184func (c *generationClient) DeleteAll(context.Context) error {
185 return fmt.Errorf("%w: DeleteAll refused", ErrGenerationBinding)
186}
187
188type generationReader struct {
189 io.ReadCloser
190 cancel context.CancelFunc
191}
192
193func (r *generationReader) Close() error { err := r.ReadCloser.Close(); r.cancel(); return err }
194
195type generationIterator struct {
196 ltx.FileIterator
197 cancel context.CancelFunc
198}
199
200func (i *generationIterator) Close() error { err := i.FileIterator.Close(); i.cancel(); return err }
201