carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "errors"
6 "sort"
7
8 "github.com/carlosframework/platform/internal/park"
9)
10
11// LeaseSet names locks in the engine's data bucket. A moves/<host>[/<op>]
12// scope needs its own lock; staging writers are bounded by Park.EvidenceAt.
13// An instances/<host>[/<inc>] scope needs its replica lease and scope lock
14// (the same location, deduplicated). Deployment-bucket staging leases must
15// never be expressed as data-bucket names. An empty set refuses.
16type LeaseSet func(context.Context, string, string) ([]string, error)
17
18// AggregateLeases adapts a renewing lease implementation to park.Leases.
19// Constituents are acquired in lexical order so competing workers cannot
20// deadlock by discovering the same set in a different order.
21type AggregateLeases struct {
22 Leases park.Leases
23 Resolve LeaseSet
24}
25
26func (a AggregateLeases) Acquire(ctx context.Context, bucket, scope string) (park.HeldLease, error) {
27 return a.acquire(ctx, bucket, scope, nil)
28}
29func (a AggregateLeases) AcquireHeld(ctx context.Context, bucket, scope string, own park.HeldLease) (park.HeldLease, error) {
30 bound, ok := own.(interface{ LeaseScope() (string, string) })
31 if !ok || own == nil {
32 return nil, ErrUnsupported
33 }
34 b, s := bound.LeaseScope()
35 if b != bucket || s != scope {
36 return nil, ErrChanged
37 }
38 if err := own.Check(ctx); err != nil {
39 return nil, err
40 }
41 return a.acquire(ctx, bucket, scope, own)
42}
43func (a AggregateLeases) acquire(ctx context.Context, bucket, scope string, own park.HeldLease) (park.HeldLease, error) {
44 if ctx == nil || a.Leases == nil || a.Resolve == nil || bucket == "" || scope == "" {
45 return nil, ErrUnsupported
46 }
47 names, err := a.Resolve(ctx, bucket, scope)
48 if err != nil {
49 return nil, err
50 }
51 names = append([]string(nil), names...)
52 sort.Strings(names)
53 uniq := names[:0]
54 hasScope := false
55 for _, name := range names {
56 if name == "" {
57 return nil, ErrUnsupported
58 }
59 if len(uniq) == 0 || uniq[len(uniq)-1] != name {
60 uniq = append(uniq, name)
61 }
62 if name == scope {
63 hasScope = true
64 }
65 }
66 if len(uniq) == 0 || !hasScope {
67 return nil, ErrUnsupported
68 }
69 held := &aggregateHeld{bucket: bucket, scope: scope}
70 for _, name := range uniq {
71 var h park.HeldLease
72 var acquireErr error
73 if own != nil && name == scope {
74 h = borrowedLease{own}
75 } else {
76 h, acquireErr = a.Leases.Acquire(ctx, bucket, name)
77 }
78 if acquireErr != nil {
79 return nil, errors.Join(acquireErr, held.releaseAndClose(ctx))
80 }
81 if h == nil {
82 return nil, errors.Join(ErrUnsupported, held.releaseAndClose(ctx))
83 }
84 held.leases = append(held.leases, h)
85 }
86 return held, nil
87}
88
89type aggregateHeld struct {
90 bucket, scope string
91 leases []park.HeldLease
92 closed bool
93}
94
95func (h *aggregateHeld) Check(ctx context.Context) error {
96 if h == nil || h.closed || len(h.leases) == 0 {
97 return ErrChanged
98 }
99 var errs []error
100 for _, lease := range h.leases {
101 errs = append(errs, lease.Check(ctx))
102 }
103 return errors.Join(errs...)
104}
105
106func (h *aggregateHeld) Release(ctx context.Context) error {
107 if h == nil || h.closed || len(h.leases) == 0 {
108 return ErrChanged
109 }
110 var errs []error
111 for i := len(h.leases) - 1; i >= 0; i-- {
112 errs = append(errs, h.leases[i].Release(ctx))
113 }
114 return errors.Join(errs...)
115}
116
117func (h *aggregateHeld) Close() {
118 if h == nil || h.closed {
119 return
120 }
121 h.closed = true
122 for i := len(h.leases) - 1; i >= 0; i-- {
123 h.leases[i].Close()
124 }
125}
126
127func (h *aggregateHeld) releaseAndClose(ctx context.Context) error {
128 var errs []error
129 for i := len(h.leases) - 1; i >= 0; i-- {
130 errs = append(errs, h.leases[i].Release(ctx))
131 h.leases[i].Close()
132 }
133 h.closed = true
134 return errors.Join(errs...)
135}
136
137func (h *aggregateHeld) LeaseScope() (string, string) { return h.bucket, h.scope }
138
139type borrowedLease struct{ park.HeldLease }
140
141func (b borrowedLease) Release(ctx context.Context) error { return b.Check(ctx) }
142func (b borrowedLease) Close() {}
143func (b borrowedLease) Context(ctx context.Context) context.Context {
144 if bound, ok := b.HeldLease.(interface {
145 Context(context.Context) context.Context
146 }); ok {
147 return bound.Context(ctx)
148 }
149 return ctx
150}
151