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 "sync"
7 "time"
8
9 "github.com/benbjohnson/litestream"
10 lss3 "github.com/benbjohnson/litestream/s3"
11)
12
13var (
14 ErrLost = errors.New("lease safety deadline elapsed")
15 ErrSpent = errors.New("lease handle is spent")
16 ErrBusy = errors.New("lease operation already running")
17)
18
19const terminationMargin = 5 * time.Second
20
21// RenewMargin is the margin Held.SafeUntil subtracts from the lease's
22// expiry at production TTLs: SafeUntil is at most TTL-RenewMargin ahead of
23// a fresh acquire. The moves staging guard reasons from it.
24const RenewMargin = terminationMargin
25
26// Production uses the fixed margin. Short TTLs exist only through AcquireTTL
27// (tests; production callers pass LeaseTTL or the moves staging term).
28func marginForTTL(ttl time.Duration) time.Duration {
29 if ttl < 3*terminationMargin {
30 return ttl / 10
31 }
32 return terminationMargin
33}
34
35type renewer struct {
36 ctx context.Context
37 leaser *lss3.Leaser
38 mu sync.Mutex
39 lease *litestream.Lease
40 err error
41 until time.Time
42 margin time.Duration
43 stopped bool
44 cancel context.CancelFunc
45 done chan struct{}
46 watchDone chan struct{}
47 changed chan struct{}
48 lost chan struct{}
49}
50
51func startRenewer(leaser *lss3.Leaser, lease *litestream.Lease, started time.Time, margin time.Duration) *renewer {
52 ctx, cancel := context.WithCancel(context.Background())
53 rn := &renewer{ctx: ctx, leaser: leaser, lease: lease, until: started.Add(leaser.TTL - margin), margin: margin, cancel: cancel, done: make(chan struct{}), watchDone: make(chan struct{}), changed: make(chan struct{}, 1), lost: make(chan struct{})}
54 go rn.watch(ctx)
55 go rn.renew(ctx)
56 return rn
57}
58
59func (rn *renewer) loseLocked(err error) {
60 if rn.err == nil {
61 rn.err = err
62 close(rn.lost)
63 }
64}
65
66// checkLocked latches loss permanently; an acknowledgement arriving after a
67// blocked renewal cannot resurrect ownership, even before the timer runs.
68func (rn *renewer) checkLocked() error {
69 if rn.err != nil {
70 return rn.err
71 }
72 if rn.stopped {
73 return ErrSpent
74 }
75 if !time.Now().Before(rn.until) {
76 rn.loseLocked(ErrLost)
77 }
78 return rn.err
79}
80func (rn *renewer) Err() error {
81 rn.mu.Lock()
82 defer rn.mu.Unlock()
83 return rn.checkLocked()
84}
85func (rn *renewer) SafeUntil() (time.Time, error) {
86 rn.mu.Lock()
87 defer rn.mu.Unlock()
88 if err := rn.checkLocked(); err != nil {
89 return time.Time{}, err
90 }
91 return rn.until, nil
92}
93func (rn *renewer) watch(ctx context.Context) {
94 defer close(rn.watchDone)
95 for {
96 until, err := rn.SafeUntil()
97 if err != nil {
98 rn.cancel()
99 return
100 }
101 timer := time.NewTimer(time.Until(until))
102 select {
103 case <-ctx.Done():
104 timer.Stop()
105 return
106 case <-rn.changed:
107 timer.Stop()
108 case <-timer.C:
109 }
110 }
111}
112func (rn *renewer) renew(ctx context.Context) {
113 defer close(rn.done)
114 every := rn.leaser.TTL / 3
115 if every <= 0 {
116 every = time.Nanosecond
117 }
118 ticker := time.NewTicker(every)
119 defer ticker.Stop()
120 for {
121 select {
122 case <-ctx.Done():
123 return
124 case <-ticker.C:
125 rn.mu.Lock()
126 err := rn.checkLocked()
127 cur := rn.lease
128 rn.mu.Unlock()
129 if err != nil {
130 rn.cancel()
131 return
132 }
133 started := time.Now()
134 renewed, err := rn.leaser.RenewLease(ctx, cur)
135 rn.mu.Lock()
136 health := rn.checkLocked()
137 // Retain the newest ETag solely for conditional cleanup, including a late
138 // success. It never repairs a latched loss or extends a stopped handle.
139 if err == nil {
140 rn.lease = renewed
141 }
142 if health == nil {
143 if err != nil {
144 rn.loseLocked(err)
145 } else {
146 rn.until = started.Add(rn.leaser.TTL - rn.margin)
147 rn.checkLocked()
148 }
149 }
150 failed := rn.err != nil || rn.stopped
151 rn.mu.Unlock()
152 if failed {
153 rn.cancel()
154 return
155 }
156 select {
157 case rn.changed <- struct{}{}:
158 default:
159 }
160 }
161 }
162}
163
164func (rn *renewer) stop() *litestream.Lease {
165 rn.mu.Lock()
166 rn.checkLocked()
167 rn.stopped = true
168 rn.mu.Unlock()
169 rn.cancel()
170 <-rn.done
171 <-rn.watchDone
172 rn.mu.Lock()
173 defer rn.mu.Unlock()
174 return rn.lease
175}
176