From fe0a357624563bb367d83e230633e6eb12973df2 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 23 Jul 2026 12:49:29 -0700 Subject: [PATCH] exclusive_locks: clear renew-running flag before dropping isLocked (#10413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On renewal failure the renew goroutine stored isLocked=false before its deferred renewGoroutineRunning.Store(false) ran. A concurrent RequestLock interleaving there reacquires the lease (sees isLocked=false), sets isLocked=true, then its CompareAndSwap on renewGoroutineRunning fails because the old goroutine's flag is still set — so no replacement renewer starts. The lock is then held locally with nothing renewing it, and silently expires on the master after the lease TTL, admitting a second holder. Clear renewGoroutineRunning before isLocked on the failure path so the reacquire path always starts a fresh renewer. Builds and vets clean; no behavior change on the success path. Claude-Session: https://claude.ai/code/session_01Ks16jnt4S7gdDk8cheQ3xu --- weed/wdclient/exclusive_locks/exclusive_locker.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/weed/wdclient/exclusive_locks/exclusive_locker.go b/weed/wdclient/exclusive_locks/exclusive_locker.go index f7c56adb2..e45775165 100644 --- a/weed/wdclient/exclusive_locks/exclusive_locker.go +++ b/weed/wdclient/exclusive_locks/exclusive_locker.go @@ -93,13 +93,15 @@ func (l *ExclusiveLocker) RequestLock(clientName string) { if l.renewGoroutineRunning.CompareAndSwap(false, true) { // start a goroutine to renew the lease go func() { - defer l.renewGoroutineRunning.Store(false) ctx2, cancel2 := context.WithCancel(context.Background()) defer cancel2() for { if err := l.renewLease(ctx2); err != nil { glog.Warningf("Failed to renew lock %s: %v", l.lockName, err) + // clear the running flag before isLocked, so a RequestLock that + // reacquires (once isLocked is false) starts a replacement renewer + l.renewGoroutineRunning.Store(false) l.isLocked.Store(false) return }