1package daemon
2
3import (
4 "github.com/bnema/vev/internal/domain"
5 "github.com/bnema/vev/internal/ports"
6)
7
8const maxResizeRetryAttempts = 3
9
10// Resize deadlines and retries retain coordinator-owned attachment, epoch, and
11// token validation at every callback effect boundary.
12func (c *renderCoordinator) recordResizeRequest(size domain.Size, source *attachedClient) uint64 {
13 return c.recordResizeRequestForLeaseWithClaim(size, source, c.attachmentLease(source), nil)
14}
15
16func (c *renderCoordinator) recordResizeRequestForLease(size domain.Size, source *attachedClient, lease *attachmentLease) uint64 {
17 return c.recordResizeRequestForLeaseWithClaim(size, source, lease, nil)
18}
19
20func (c *renderCoordinator) recordResizeRequestForLeaseWithClaim(size domain.Size, source *attachedClient, lease *attachmentLease, geometryClaim *sharedPTYClaim) uint64 {
21 c.mu.Lock()
22 if !c.leaseCurrentLocked(lease, false) || lease.attachment != source {
23 c.mu.Unlock()
24 return 0
25 }
26 _, retry := c.retryLane.replaceLocked()
27 c.resize.epoch++
28 c.resize.size, c.resize.source, c.resize.lease = size, source, lease
29 c.resize.geometryClaim = geometryClaim
30 c.resize.retryAttempts = 0
31 epoch := c.resize.epoch
32 c.mu.Unlock()
33 stopDetachedTimer(retry)
34 return epoch
35}
36
37func (c *renderCoordinator) scheduleResizeForLease(size domain.Size, source *attachedClient, lease *attachmentLease, run func(uint64)) uint64 {
38 return c.scheduleResizeForLeaseWithClaim(size, source, lease, nil, run)
39}
40
41func (c *renderCoordinator) scheduleResizeForLeaseWithClaim(size domain.Size, source *attachedClient, lease *attachmentLease, geometryClaim *sharedPTYClaim, run func(uint64)) uint64 {
42 epoch := c.recordResizeRequestForLeaseWithClaim(size, source, lease, geometryClaim)
43 if epoch == 0 {
44 return 0
45 }
46 c.mu.Lock()
47 gen, old := c.resizeLane.replaceLocked()
48 clock := c.opts.clock
49 c.mu.Unlock()
50 stopDetachedTimer(old)
51 if clock == nil {
52 if c.resizeCurrentForLease(epoch, source, lease, false) {
53 run(epoch)
54 }
55 return epoch
56 }
57 timer := clock.NewTimer(minOutputRenderDeadline)
58 timerC := timer.C()
59 if timerC == nil {
60 timer.Stop()
61 if c.resizeCurrentForLease(epoch, source, lease, false) {
62 run(epoch)
63 }
64 return epoch
65 }
66 c.mu.Lock()
67 valid := !c.torndown && c.resizeLane.generation == gen && c.leaseCurrentLocked(lease, false) && c.resize.lease == lease && c.resize.epoch == epoch && c.resize.source == source
68 var token *timerToken
69 if valid {
70 token = c.resizeLane.publishLocked(gen, timer)
71 valid = token != nil
72 if valid {
73 c.supervisor.startLocked(token, timerC, func() {
74 c.mu.Lock()
75 valid := !c.torndown && c.resizeLane.token == token && c.resizeLane.generation == token.generation && c.leaseCurrentLocked(lease, false) && c.resize.lease == lease && c.resize.epoch == epoch && c.resize.source == source
76 if valid {
77 c.resizeLane.clearLocked(token)
78 }
79 c.mu.Unlock()
80 if valid {
81 run(epoch)
82 }
83 })
84 }
85 }
86 c.mu.Unlock()
87 if !valid {
88 timer.Stop()
89 }
90 return epoch
91}
92
93func (c *renderCoordinator) resizeCurrentForLease(epoch uint64, source *attachedClient, lease *attachmentLease, commit bool) bool {
94 c.mu.Lock()
95 defer c.mu.Unlock()
96 if !c.leaseCurrentLocked(lease, false) || lease.attachment != source || c.resize.lease != lease || c.resize.source != source || c.resize.epoch != epoch {
97 return false
98 }
99 if commit {
100 if c.resize.committed >= epoch {
101 return false
102 }
103 c.resize.committed = epoch
104 }
105 return true
106}
107
108type resizeRetryReservation struct {
109 coordinator *renderCoordinator
110 epoch uint64
111 source *attachedClient
112 lease *attachmentLease
113 run func()
114 generation uint64
115 old *timerToken
116 clock ports.Clock
117}
118
119// reserveResizeRetryForLease publishes the retry-lane generation only. Timer
120// calls are deferred to finish so owner fences protect no external operation.
121func (c *renderCoordinator) reserveResizeRetryForLease(epoch uint64, source *attachedClient, lease *attachmentLease, run func()) (*resizeRetryReservation, bool) {
122 c.mu.Lock()
123 defer c.mu.Unlock()
124 if c.torndown || !c.leaseCurrentLocked(lease, false) || lease.attachment != source || c.resize.lease != lease || c.resize.source != source || c.resize.epoch != epoch || c.resize.committed != epoch || c.resize.retryAttempts >= maxResizeRetryAttempts {
125 return nil, false
126 }
127 c.resize.retryAttempts++
128 gen, old := c.retryLane.replaceLocked()
129 return &resizeRetryReservation{coordinator: c, epoch: epoch, source: source, lease: lease, run: run, generation: gen, old: old, clock: c.opts.clock}, true
130}
131
132func (r *resizeRetryReservation) finish() {
133 if r == nil || r.coordinator == nil {
134 return
135 }
136 c := r.coordinator
137 stopDetachedTimer(r.old)
138 if r.clock == nil {
139 r.run()
140 return
141 }
142 timer := r.clock.NewTimer(minOutputRenderDeadline)
143 timerC := timer.C()
144 if timerC == nil {
145 timer.Stop()
146 return
147 }
148 c.mu.Lock()
149 valid := !c.torndown && c.retryLane.generation == r.generation && c.leaseCurrentLocked(r.lease, false) && c.resize.lease == r.lease && c.resize.epoch == r.epoch && c.resize.committed == r.epoch && c.resize.source == r.source
150 if valid {
151 token := c.retryLane.publishLocked(r.generation, timer)
152 valid = token != nil
153 if valid {
154 c.supervisor.startLocked(token, timerC, func() {
155 c.mu.Lock()
156 valid := !c.torndown && c.retryLane.token == token && c.retryLane.generation == token.generation && c.leaseCurrentLocked(r.lease, false) && c.resize.lease == r.lease && c.resize.epoch == r.epoch && c.resize.committed == r.epoch && c.resize.source == r.source
157 if valid {
158 c.retryLane.clearLocked(token)
159 }
160 c.mu.Unlock()
161 if valid {
162 r.run()
163 }
164 })
165 }
166 }
167 c.mu.Unlock()
168 if !valid {
169 timer.Stop()
170 }
171}
172
173func (c *renderCoordinator) scheduleResizeRetryForLease(epoch uint64, source *attachedClient, lease *attachmentLease, run func()) {
174 reservation, ok := c.reserveResizeRetryForLease(epoch, source, lease, run)
175 if !ok {
176 return
177 }
178 reservation.finish()
179}
180
181func (c *renderCoordinator) resizeRetryAvailableForLease(epoch uint64, source *attachedClient, lease *attachmentLease) bool {
182 c.mu.Lock()
183 defer c.mu.Unlock()
184 return !c.torndown && c.leaseCurrentLocked(lease, false) && lease.attachment == source && c.resize.lease == lease && c.resize.source == source && c.resize.epoch == epoch && c.resize.committed == epoch && c.resize.retryAttempts < maxResizeRetryAttempts
185}
186
187func (c *renderCoordinator) retryCurrentForLease(epoch uint64, source *attachedClient, lease *attachmentLease) bool {
188 c.mu.Lock()
189 defer c.mu.Unlock()
190 return c.leaseCurrentLocked(lease, false) && lease.attachment == source && c.resize.lease == lease && c.resize.source == source && c.resize.epoch == epoch && c.resize.committed == epoch
191}
192
193func (c *renderCoordinator) resizeSnapshot() resizeRequestMetadata {
194 c.mu.Lock()
195 defer c.mu.Unlock()
196 return c.resize
197}