Skip to content

Commit 96b0ad3

Browse files
liyishuaiclaude
andcommitted
lease: fix Renew/Revoke race by closing revokec eagerly (Issue 14758)
Close revokec at the start of Revoke() (while holding le.mu) instead of deferring it to function exit. This signals "revocation has started" before keys are deleted. In Renew(), add a non-blocking check on revokec after re-acquiring le.mu and confirming the lease exists in leaseMap. If revokec is closed, Renew returns ErrLeaseNotFound instead of refreshing the lease. Both operations happen under le.mu, so they are properly ordered: once Revoke closes revokec, any concurrent Renew will see it and refuse to refresh. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Yishuai Li <yishuai.li@pingcap.com>
1 parent b76056d commit 96b0ad3

File tree

1 file changed

+12
-1
lines changed

1 file changed

+12
-1
lines changed

server/lease/lessor.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,10 @@ func (le *lessor) Revoke(id LeaseID) error {
332332
return ErrLeaseNotFound
333333
}
334334

335-
defer close(l.revokec)
335+
// Signal that revocation has started. This must happen while holding
336+
// le.mu so that Renew's revokec check (also under le.mu) is ordered
337+
// with respect to this close.
338+
close(l.revokec)
336339
// unlock before doing external work
337340
le.mu.Unlock()
338341

@@ -448,6 +451,14 @@ func (le *lessor) Renew(id LeaseID) (int64, error) {
448451
le.mu.Unlock()
449452
return -1, ErrLeaseNotFound
450453
}
454+
// Check if Revoke() has started. revokec is closed under le.mu in
455+
// Revoke(), so this check is properly ordered.
456+
select {
457+
case <-l.revokec:
458+
le.mu.Unlock()
459+
return -1, ErrLeaseNotFound
460+
default:
461+
}
451462
l.refresh(0)
452463
item := &LeaseWithTime{id: l.ID, time: l.expiry}
453464
le.leaseExpiredNotifier.RegisterOrUpdate(item)

0 commit comments

Comments
 (0)