mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-10 16:40:46 +02:00
ObjectTransaction forwards to the ring owner so one filer's per-path lock arbitrates every writer of a key. But a ring change hands the key over before the new owner has rebuilt the locks the prior owner still holds, so for the cooling-off window both can grant it. LockRing.PriorOwner exists for exactly this and nothing consulted it. Route to the prior owner while that window is open. LockRing.WriteOwner resolves prior-else-current under one read lock, so the pair cannot come from different rings and name the same filer twice. An unreachable owner fails the request rather than falling back to the current one. gRPC reports a response lost in transit as Unavailable, indistinguishable from a request the owner never saw, so re-sending elsewhere could re-apply what the owner already committed; and an owner unreachable from here may be partitioned rather than down, still serving the key to everyone else — which is the split brain the routing exists to prevent. The window is bounded: once it closes the ring hands the key to its new owner. The owner resolution and the forward move into writeOwner/forwardToWriteOwner so the next routed RPC reuses them rather than copying the block. Claude-Session: https://claude.ai/code/session_01Fx1Hx8RqsJqHpbfbgTf4WJ
212 lines
6.2 KiB
Go
212 lines
6.2 KiB
Go
package lock_manager
|
|
|
|
import (
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
)
|
|
|
|
type LockRingSnapshot struct {
|
|
servers []pb.ServerAddress
|
|
ts time.Time
|
|
ring *HashRing // prebuilt ring for servers, so PriorOwner need not rebuild per call
|
|
}
|
|
|
|
type LockRing struct {
|
|
sync.RWMutex
|
|
snapshots []*LockRingSnapshot
|
|
lastCompactTime time.Time
|
|
snapshotInterval time.Duration
|
|
onTakeSnapshot func(snapshot []pb.ServerAddress)
|
|
cleanupWg sync.WaitGroup
|
|
Ring *HashRing // consistent hash ring
|
|
version int64 // monotonic version from master, rejects stale updates
|
|
}
|
|
|
|
func NewLockRing(snapshotInterval time.Duration) *LockRing {
|
|
return &LockRing{
|
|
snapshotInterval: snapshotInterval,
|
|
Ring: NewHashRing(DefaultVnodeCount),
|
|
}
|
|
}
|
|
|
|
func (r *LockRing) SetTakeSnapshotCallback(onTakeSnapshot func(snapshot []pb.ServerAddress)) {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
r.onTakeSnapshot = onTakeSnapshot
|
|
}
|
|
|
|
// SetSnapshot replaces the ring with a new server list from the master.
|
|
// The version must be >= the current version, otherwise the update is rejected
|
|
// (protects against reordered messages). Version 0 is always accepted (bootstrap).
|
|
func (r *LockRing) SetSnapshot(servers []pb.ServerAddress, version int64) bool {
|
|
|
|
sort.Slice(servers, func(i, j int) bool {
|
|
return servers[i] < servers[j]
|
|
})
|
|
|
|
r.Lock()
|
|
if version > 0 && version < r.version {
|
|
glog.V(0).Infof("LockRing: rejecting stale update v%d (current v%d)", version, r.version)
|
|
r.Unlock()
|
|
return false
|
|
}
|
|
r.version = version
|
|
// Update the ring while holding the lock so version and ring state
|
|
// are always consistent — prevents a concurrent SetSnapshot from
|
|
// seeing the new version but applying its servers to the old ring.
|
|
r.Ring.SetServers(servers)
|
|
// Append the snapshot under the same lock as the ring update so a concurrent
|
|
// PriorOwner always sees snapshots[0] matching r.Ring (and snapshots[1] as the
|
|
// true prior); otherwise it could pair a new ring with a stale prior snapshot.
|
|
r.addOneSnapshotLocked(servers)
|
|
r.Unlock()
|
|
|
|
r.cleanupWg.Add(1)
|
|
go func() {
|
|
defer r.cleanupWg.Done()
|
|
<-time.After(r.snapshotInterval)
|
|
r.compactSnapshots()
|
|
}()
|
|
return true
|
|
}
|
|
|
|
// Version returns the current ring version.
|
|
func (r *LockRing) Version() int64 {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
return r.version
|
|
}
|
|
|
|
// addOneSnapshotLocked appends a new snapshot (newest at index 0). The caller
|
|
// must hold r.Lock(), so the ring update and snapshot append are one atomic step.
|
|
func (r *LockRing) addOneSnapshotLocked(servers []pb.ServerAddress) {
|
|
ts := time.Now()
|
|
ring := NewHashRing(DefaultVnodeCount)
|
|
ring.SetServers(servers)
|
|
t := &LockRingSnapshot{
|
|
servers: servers,
|
|
ts: ts,
|
|
ring: ring,
|
|
}
|
|
r.snapshots = append(r.snapshots, t)
|
|
for i := len(r.snapshots) - 2; i >= 0; i-- {
|
|
r.snapshots[i+1] = r.snapshots[i]
|
|
}
|
|
r.snapshots[0] = t
|
|
|
|
if r.onTakeSnapshot != nil {
|
|
r.onTakeSnapshot(t.servers)
|
|
}
|
|
}
|
|
|
|
func (r *LockRing) compactSnapshots() {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
ts := time.Now()
|
|
recentSnapshotIndex := 1
|
|
for ; recentSnapshotIndex < len(r.snapshots); recentSnapshotIndex++ {
|
|
if ts.Sub(r.snapshots[recentSnapshotIndex].ts) > r.snapshotInterval {
|
|
break
|
|
}
|
|
}
|
|
if recentSnapshotIndex+1 <= len(r.snapshots) {
|
|
r.snapshots = r.snapshots[:recentSnapshotIndex+1]
|
|
}
|
|
r.lastCompactTime = ts
|
|
}
|
|
|
|
func (r *LockRing) GetSnapshot() (servers []pb.ServerAddress) {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
|
|
if len(r.snapshots) == 0 {
|
|
return
|
|
}
|
|
return r.snapshots[0].servers
|
|
}
|
|
|
|
// WaitForCleanup waits for all pending cleanup operations to complete
|
|
func (r *LockRing) WaitForCleanup() {
|
|
r.cleanupWg.Wait()
|
|
}
|
|
|
|
// GetSnapshotCount safely returns the number of snapshots for testing
|
|
func (r *LockRing) GetSnapshotCount() int {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
return len(r.snapshots)
|
|
}
|
|
|
|
// GetPrimaryAndBackup returns the primary and backup servers for a key
|
|
// using the consistent hash ring.
|
|
func (r *LockRing) GetPrimaryAndBackup(key string) (primary, backup pb.ServerAddress) {
|
|
return r.Ring.GetPrimaryAndBackup(key)
|
|
}
|
|
|
|
// GetPrimary returns the primary server for a key using the consistent hash ring.
|
|
func (r *LockRing) GetPrimary(key string) pb.ServerAddress {
|
|
return r.Ring.GetPrimary(key)
|
|
}
|
|
|
|
// PriorOwner returns the key's owner from the previous ring snapshot, but only
|
|
// while the ring changed within the last snapshotInterval and that owner differs
|
|
// from the current primary. This is the cooling-off window in which the previous
|
|
// owner may still hold locks the new owner has not yet rebuilt — a caller can
|
|
// consult it before granting so a fresh owner does not double-grant during a
|
|
// rebalance. Returns "" outside the window or when ownership did not move. It
|
|
// uses the snapshot's prebuilt ring, so it does not rebuild a hash ring per call.
|
|
func (r *LockRing) PriorOwner(key string) pb.ServerAddress {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
return r.priorOwnerLocked(key)
|
|
}
|
|
|
|
// WriteOwner returns the filer that should serialize writes to key: the prior
|
|
// owner while a ring change is still within the cooling-off window, otherwise
|
|
// the current primary. Both are read under one lock so the pair cannot come
|
|
// from different rings, which could otherwise name the same filer twice.
|
|
func (r *LockRing) WriteOwner(key string) pb.ServerAddress {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
if prior := r.priorOwnerLocked(key); prior != "" {
|
|
return prior
|
|
}
|
|
return r.Ring.GetPrimary(key)
|
|
}
|
|
|
|
// priorOwnerLocked is PriorOwner's body; the caller holds at least RLock.
|
|
func (r *LockRing) priorOwnerLocked(key string) pb.ServerAddress {
|
|
if len(r.snapshots) < 2 {
|
|
return ""
|
|
}
|
|
if time.Since(r.snapshots[0].ts) > r.snapshotInterval {
|
|
return ""
|
|
}
|
|
current := r.Ring.GetPrimary(key)
|
|
var prior pb.ServerAddress
|
|
if pr := r.snapshots[1].ring; pr != nil {
|
|
prior = pr.GetPrimary(key)
|
|
} else {
|
|
prior = hashKeyToServer(key, r.snapshots[1].servers)
|
|
}
|
|
if prior != "" && prior != current {
|
|
return prior
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// hashKeyToServer uses a temporary consistent hash ring for the given server list.
|
|
func hashKeyToServer(key string, servers []pb.ServerAddress) pb.ServerAddress {
|
|
if len(servers) == 0 {
|
|
return ""
|
|
}
|
|
ring := NewHashRing(DefaultVnodeCount)
|
|
ring.SetServers(servers)
|
|
return ring.GetPrimary(key)
|
|
}
|