Skip to content

Commit 9588d26

Browse files
committed
fix: reorder loss handling (cleanup before notify)
1 parent 8015825 commit 9588d26

File tree

2 files changed

+42
-5
lines changed

2 files changed

+42
-5
lines changed

pkg/manager/leaseguard.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func (g *leaseGuard) TryAcquire(ctx context.Context) bool {
135135
}
136136

137137
// Internal: renew loop and single-step renew. If renewal observes a different,
138-
// valid holder or API errors persist, onLost() is invoked once and the fence is released.
138+
// valid holder or API errors persist, the fence is released first and onLost() is invoked once.
139139
func (g *leaseGuard) renewLoop(ctx context.Context, key types.NamespacedName) {
140140
t := time.NewTicker(g.renew)
141141
defer t.Stop()
@@ -145,11 +145,11 @@ func (g *leaseGuard) renewLoop(ctx context.Context, key types.NamespacedName) {
145145
return
146146
case <-t.C:
147147
if ok := g.renewOnce(ctx, key); !ok {
148-
// best-effort notify once, then release
148+
// best-effort: release, then notify once
149+
g.Release(context.Background())
149150
if g.onLost != nil {
150151
g.onLost()
151152
}
152-
g.Release(context.Background())
153153
return
154154
}
155155
}

pkg/manager/leaseguard_test.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func TestTryAcquire_AdoptsWhenExpired(t *testing.T) {
140140
}
141141
}
142142

143-
func TestRenewLoop_OnLostTriggersCallbackAndReleases(t *testing.T) {
143+
func TestRenewLoop_LossTriggersOnLostAndReleases(t *testing.T) {
144144
s := newScheme(t)
145145
c := fake.NewClientBuilder().WithScheme(s).Build()
146146

@@ -171,7 +171,6 @@ func TestRenewLoop_OnLostTriggersCallbackAndReleases(t *testing.T) {
171171
case <-time.After(2 * time.Second):
172172
t.Fatalf("expected onLost to be called after renewal detects loss")
173173
}
174-
175174
if g.held {
176175
t.Fatalf("guard should not be held after loss")
177176
}
@@ -186,3 +185,41 @@ func TestRelease_NoHoldIsNoop(t *testing.T) {
186185
g.Release(context.Background())
187186
// Nothing to assert other than "does not panic"
188187
}
188+
189+
func TestRenewLoop_ReleasesBeforeOnLost(t *testing.T) {
190+
s := newScheme(t)
191+
c := fake.NewClientBuilder().WithScheme(s).Build()
192+
193+
heldAtCallback := make(chan bool, 1)
194+
var g *leaseGuard
195+
g = newLeaseGuard(c, testNS, testName, testID, 3*time.Second, 50*time.Millisecond, func() {
196+
heldAtCallback <- g.held
197+
})
198+
199+
// Acquire
200+
if ok := g.TryAcquire(context.Background()); !ok {
201+
t.Fatalf("expected TryAcquire to succeed")
202+
}
203+
204+
// Flip lease to another holder so renewOnce observes loss.
205+
ls := getLease(t, c)
206+
now := metav1.NowMicro()
207+
dur := int32(3)
208+
other := otherID
209+
ls.Spec.HolderIdentity = &other
210+
ls.Spec.LeaseDurationSeconds = &dur
211+
ls.Spec.RenewTime = &now
212+
if err := c.Update(context.Background(), ls); err != nil {
213+
t.Fatalf("update lease: %v", err)
214+
}
215+
216+
// Expect callback to observe held == false (Release runs before onLost)
217+
select {
218+
case v := <-heldAtCallback:
219+
if v {
220+
t.Fatalf("expected g.held=false at onLost callback time")
221+
}
222+
case <-time.After(2 * time.Second):
223+
t.Fatalf("expected onLost to be called after renewal detects loss")
224+
}
225+
}

0 commit comments

Comments
 (0)