Files
s3/weed/cluster/lock_manager/distributed_lock_manager.go
zooqueen dd74d3e6d6 Replace AGPL MinIO fork with Apache-2.0 SeaweedFS fork; rebrand CLI weed->s3
hanzoai/s3 was an AGPL-3.0 MinIO fork whose network-copyleft blocks embedding
and reselling as a managed service. Replace it wholesale with a fork of
SeaweedFS (github.com/seaweedfs/seaweedfs, Apache-2.0, Go) which ships a
built-in S3 gateway + Filer.

- License: AGPL-3.0 -> Apache-2.0 (upstream LICENSE kept verbatim, incl.
  "Copyright 2025 Chris Lu"); NOTICE added crediting SeaweedFS. No AGPL/BUSL/
  SSPL/GPL or enterprise ("ee") files remain.
- Module: go.mod path set to github.com/hanzoai/s3 with a self-replace
  (replace github.com/seaweedfs/seaweedfs => ./) so the ~1800 internal Go files
  keep their upstream import paths with zero churn. Internal identifiers and
  import paths are intentionally NOT renamed.
- CLI rebrand: the binary is built as `s3` (was `weed`); root usage, per-command
  help/usage/flag strings, shell autocomplete program name, and the product name
  in CLI output now read "Hanzo S3" / `s3`. Users never see `weed`.
  (e.g. `s3 server`, `s3 filer`, `s3 master`, `s3 volume`, `s3 s3`, `s3 mount`.)
- Build: `make` (or `go build -o s3 ./weed`) builds the `s3` binary green;
  `cmd/weed-db` and `cmd/weed-sql` also build; `go vet ./weed/command/` clean.
- README/LLM.md rewritten for Hanzo S3; AGENTS.md/CLAUDE.md symlinks preserved.
- Dropped a 33MB prebuilt Mach-O `stress` binary upstream had committed; `.gitignore`
  updated to ignore the `s3` build output.
2026-06-18 23:41:25 -07:00

193 lines
6.7 KiB
Go

package lock_manager
import (
"fmt"
"time"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb"
)
const RenewInterval = time.Second * 3
const LiveLockTTL = time.Second * 7
var NoLockServerError = fmt.Errorf("no lock server found")
// ReplicateFunc is called to replicate a lock operation to a backup server.
// The caller (filer server) provides this to avoid a circular dependency.
// seq is a per-lock monotonic sequence number for causal ordering — the backup
// rejects mutations with seq <= its current seq for that key.
type ReplicateFunc func(server pb.ServerAddress, key string, expiredAtNs int64, token string, owner string, generation int64, seq int64, isUnlock bool)
type DistributedLockManager struct {
lockManager *LockManager
LockRing *LockRing
Host pb.ServerAddress
ReplicateFn ReplicateFunc // set by filer server after creation
}
func NewDistributedLockManager(host pb.ServerAddress) *DistributedLockManager {
return &DistributedLockManager{
lockManager: NewLockManager(),
LockRing: NewLockRing(time.Second * 5),
Host: host,
}
}
func (dlm *DistributedLockManager) LockWithTimeout(key string, expiredAtNs int64, token string, owner string) (lockOwner string, renewToken string, generation int64, movedTo pb.ServerAddress, err error) {
primary, _ := dlm.LockRing.GetPrimaryAndBackup(key)
if primary == "" {
err = NoLockServerError
return
}
if primary != dlm.Host {
// If this is a renewal (non-empty token) and we still hold the lock locally,
// serve it here rather than redirecting. This handles the window between
// ring update and lock transfer completion — the old primary remains
// authoritative for locks it still holds.
if token != "" {
if lock, found := dlm.lockManager.GetLock(key); found && !lock.IsBackup && lock.Token == token {
var seq int64
lockOwner, renewToken, generation, seq, err = dlm.lockManager.Lock(key, expiredAtNs, token, owner)
if err == nil && renewToken != "" {
dlm.replicateToBackup(key, expiredAtNs, renewToken, owner, generation, seq, false)
}
return
}
}
movedTo = primary
return
}
var seq int64
lockOwner, renewToken, generation, seq, err = dlm.lockManager.Lock(key, expiredAtNs, token, owner)
if err == nil && renewToken != "" {
dlm.replicateToBackup(key, expiredAtNs, renewToken, owner, generation, seq, false)
}
return
}
func (dlm *DistributedLockManager) FindLockOwner(key string) (owner string, movedTo pb.ServerAddress, err error) {
primary, _ := dlm.LockRing.GetPrimaryAndBackup(key)
if primary == "" {
err = NoLockServerError
return
}
if primary != dlm.Host {
// If we still hold this lock locally, serve it here
if lock, found := dlm.lockManager.GetLock(key); found && !lock.IsBackup {
owner = lock.Owner
return
}
movedTo = primary
servers := dlm.LockRing.GetSnapshot()
glog.V(0).Infof("lock %s not on current %s but on %s from %v", key, dlm.Host, movedTo, servers)
return
}
owner, err = dlm.lockManager.GetLockOwner(key)
return
}
func (dlm *DistributedLockManager) Unlock(key string, token string) (movedTo pb.ServerAddress, err error) {
primary, _ := dlm.LockRing.GetPrimaryAndBackup(key)
if primary == "" {
err = NoLockServerError
return
}
if primary != dlm.Host {
// If we still hold this lock locally, serve the unlock here
if lock, found := dlm.lockManager.GetLock(key); found && !lock.IsBackup && lock.Token == token {
var isUnlocked bool
var generation int64
var seq int64
isUnlocked, generation, seq, err = dlm.lockManager.Unlock(key, token)
if isUnlocked {
dlm.replicateToBackup(key, 0, "", "", generation, seq, true)
}
return
}
movedTo = primary
return
}
var isUnlocked bool
var generation int64
var seq int64
isUnlocked, generation, seq, err = dlm.lockManager.Unlock(key, token)
if isUnlocked {
dlm.replicateToBackup(key, 0, "", "", generation, seq, true)
}
return
}
// InsertLock is used to insert a lock to a server unconditionally.
// It is used when a server is down and the lock is moved to another server.
// After inserting, it replicates to the backup for this key.
func (dlm *DistributedLockManager) InsertLock(key string, expiredAtNs int64, token string, owner string, generation int64, seq int64) {
if dlm.lockManager.InsertLock(key, expiredAtNs, token, owner, generation, seq) {
dlm.replicateToBackup(key, expiredAtNs, token, owner, generation, seq, false)
}
}
// InsertBackupLock inserts a lock as a backup copy, rejecting stale seq
func (dlm *DistributedLockManager) InsertBackupLock(key string, expiredAtNs int64, token string, owner string, generation int64, seq int64) {
dlm.lockManager.InsertBackupLock(key, expiredAtNs, token, owner, generation, seq)
}
// RemoveBackupLock removes a backup lock unconditionally
func (dlm *DistributedLockManager) RemoveBackupLock(key string) {
dlm.lockManager.RemoveLock(key)
}
// RemoveBackupLockIfSeq removes a local copy only if the incoming mutation is not older.
func (dlm *DistributedLockManager) RemoveBackupLockIfSeq(key string, generation int64, seq int64) {
dlm.lockManager.RemoveBackupLockIfSeq(key, generation, seq)
}
func (dlm *DistributedLockManager) SelectNotOwnedLocks(servers []pb.ServerAddress) (locks []*Lock) {
return dlm.lockManager.SelectLocks(func(key string) bool {
server := hashKeyToServer(key, servers)
return server != dlm.Host
})
}
func (dlm *DistributedLockManager) CalculateTargetServer(key string, servers []pb.ServerAddress) pb.ServerAddress {
return hashKeyToServer(key, servers)
}
func (dlm *DistributedLockManager) IsLocal(key string) bool {
primary := dlm.LockRing.GetPrimary(key)
if primary == "" {
return true
}
return primary == dlm.Host
}
// AllLocks returns all non-expired locks on this node
func (dlm *DistributedLockManager) AllLocks() []*Lock {
return dlm.lockManager.AllLocks()
}
// PromoteLock promotes a backup lock to primary
func (dlm *DistributedLockManager) PromoteLock(key string) bool {
return dlm.lockManager.PromoteLock(key)
}
// DemoteLock demotes a primary lock to backup
func (dlm *DistributedLockManager) DemoteLock(key string) bool {
return dlm.lockManager.DemoteLock(key)
}
// GetLock returns a copy of a lock if it exists
func (dlm *DistributedLockManager) GetLock(key string) (*Lock, bool) {
return dlm.lockManager.GetLock(key)
}
// replicateToBackup asynchronously replicates a lock operation to the backup server
func (dlm *DistributedLockManager) replicateToBackup(key string, expiredAtNs int64, token string, owner string, generation int64, seq int64, isUnlock bool) {
_, backup := dlm.LockRing.GetPrimaryAndBackup(key)
if backup == "" {
return // single-server deployment, no backup
}
if dlm.ReplicateFn != nil {
go dlm.ReplicateFn(backup, key, expiredAtNs, token, owner, generation, seq, isUnlock)
}
}