diff --git a/weed/cluster/lock_client.go b/weed/cluster/lock_client.go index 44529acb0..bc5a8ac01 100644 --- a/weed/cluster/lock_client.go +++ b/weed/cluster/lock_client.go @@ -117,6 +117,7 @@ func (lc *LockClient) PriorOwnerForKey(key string) pb.ServerAddress { } type LiveLock struct { + generation int64 // fencing token from the lock server; MUST stay first so the 64-bit atomic ops stay 8-byte aligned on 32-bit ARM key string renewToken string expireAtNs int64 @@ -129,8 +130,7 @@ type LiveLock struct { lc *LockClient owner string lockTTL time.Duration - consecutiveFailures int // Track connection failures to trigger fallback - generation int64 // fencing token from the lock server + consecutiveFailures int // Track connection failures to trigger fallback } // NewShortLivedLock creates a lock with a 5-second duration diff --git a/weed/cluster/lock_client_test.go b/weed/cluster/lock_client_test.go index 608ddf6da..c53e8c6cf 100644 --- a/weed/cluster/lock_client_test.go +++ b/weed/cluster/lock_client_test.go @@ -2,6 +2,7 @@ package cluster import ( "fmt" + "sync/atomic" "testing" "time" @@ -150,3 +151,17 @@ func TestLockClientPriorOwnerForKeyExpires(t *testing.T) { } } } + +// LiveLock.generation is a fencing token written and read with 64-bit atomic +// operations. On 32-bit platforms (GOARCH=386 and GOARCH=arm) a 64-bit atomic +// op requires an 8-byte-aligned address, which Go only guarantees for the +// first field of an allocated struct. generation must therefore stay first: +// a misaligned field makes StoreInt64 panic with "unaligned 64-bit atomic +// operation" the moment this test runs on a 32-bit target. +func TestLiveLockGenerationIsAligned(t *testing.T) { + var lock LiveLock + atomic.StoreInt64(&lock.generation, 42) + if got := lock.Generation(); got != 42 { + t.Fatalf("generation = %d, want 42", got) + } +}