From 7ab73b914e825eaf720b2d78ecb6b0d468ca97d7 Mon Sep 17 00:00:00 2001 From: "user.email" Date: Mon, 26 Oct 2026 19:49:33 -0700 Subject: [PATCH] dlm: advance the mutation seq on a lost-renewal replay The replayed lease extension kept the old seq, so InsertBackupLock dropped it as stale and a promoted backup would have expired the recovered lock early. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- weed/cluster/lock_manager/lock_manager.go | 3 ++- weed/cluster/lock_manager/lock_manager_test.go | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/weed/cluster/lock_manager/lock_manager.go b/weed/cluster/lock_manager/lock_manager.go index e027254e7f..70c042238c 100644 --- a/weed/cluster/lock_manager/lock_manager.go +++ b/weed/cluster/lock_manager/lock_manager.go @@ -112,8 +112,9 @@ func (lm *LockManager) Lock(path string, expiredAtNs int64, token string, owner // instead of conflicting with its own lease until it expires. renewToken = oldValue.Token generation = oldValue.Generation - seq = oldValue.Seq + seq = oldValue.Seq + 1 oldValue.ExpiredAtNs = expiredAtNs + oldValue.Seq = seq return } if token == "" { diff --git a/weed/cluster/lock_manager/lock_manager_test.go b/weed/cluster/lock_manager/lock_manager_test.go index d8101ed435..fa1693870c 100644 --- a/weed/cluster/lock_manager/lock_manager_test.go +++ b/weed/cluster/lock_manager/lock_manager_test.go @@ -93,16 +93,26 @@ func TestLockManager_LostRenewalResponseReplaysToken(t *testing.T) { assert.NoError(t, err) // The renewal rotated token1 to token2; the client never learned it. - _, token2, _, _, err := lm.Lock("key1", expires, token1, "owner1") + _, token2, generation, seq2, err := lm.Lock("key1", expires, token1, "owner1") assert.NoError(t, err) assert.NotEqual(t, token1, token2) // Retrying with the superseded token replays the current one instead of - // conflicting with its own lease until it expires. - owner, replayed, _, _, err := lm.Lock("key1", expires, token1, "owner1") + // conflicting with its own lease until it expires. The replayed mutation + // still advances seq so backups accept the extended expiry as fresh. + later := time.Now().Add(60 * time.Second).UnixNano() + owner, replayed, _, seq3, err := lm.Lock("key1", later, token1, "owner1") assert.NoError(t, err) assert.Equal(t, "owner1", owner) assert.Equal(t, token2, replayed) + assert.Greater(t, seq3, seq2) + + backup := NewLockManager() + backup.InsertBackupLock("key1", expires, token2, "owner1", generation, seq2) + backup.InsertBackupLock("key1", later, token2, "owner1", generation, seq3) + backupLock, found := backup.GetLock("key1") + assert.True(t, found) + assert.Equal(t, later, backupLock.ExpiredAtNs) // A replay from a different owner still mismatches. _, _, _, _, err = lm.Lock("key1", expires, token1, "intruder")