mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-18 12:08:32 +02:00
* filer: POSIX advisory lock set primitive (phase 1) Pure per-inode conflict/coalesce/range-split logic for fcntl byte-range and flock whole-file locks, extracted from the mount's PosixLockTable without its wait queue or inode-map concurrency. Owner identity is (Sid, Owner) so the same FUSE owner on different mounts never aliases, and ReleaseSession reaps a dead mount's locks. The owner filer will hold one Set per inode under the per-path lock; no concurrency control here. * test: tolerate transient FUSE invisibility in ConcurrentReadWrite A concurrent truncating overwrite leaves a short-lived dentry/cache window where the file is momentarily ENOENT to another opener. Retry the reads and writes a few times before failing, as ConcurrentDirectoryOperations does. * filer: serialize the POSIX lock set for entry metadata Versioned fixed-width binary encoding of a Set, so an inode's held locks can ride in its entry metadata: a lock op materializes the Set from the blob, applies under the per-path lock, and writes it back. Empty set encodes to nil so a lock-free inode carries no blob. * filer: encode the POSIX lock set as protobuf Replace the hand-rolled fixed-width codec with a LockSetProto message, so the metadata blob can gain fields without a format-version migration. proto.Unmarshal already rejects a malformed blob, so the explicit version and length checks go away. Marshal now returns an error to match.
76 lines
2.2 KiB
Go
76 lines
2.2 KiB
Go
package posixlock
|
|
|
|
import (
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestMarshalEmptyIsNil(t *testing.T) {
|
|
s := &Set{}
|
|
b, err := s.Marshal()
|
|
if err != nil {
|
|
t.Fatalf("marshal empty: %v", err)
|
|
}
|
|
if b != nil {
|
|
t.Fatalf("empty set should marshal to nil, got %d bytes", len(b))
|
|
}
|
|
got, err := Unmarshal(nil)
|
|
if err != nil {
|
|
t.Fatalf("unmarshal nil: %v", err)
|
|
}
|
|
if !got.Empty() {
|
|
t.Fatal("unmarshal nil should be an empty set")
|
|
}
|
|
}
|
|
|
|
func TestMarshalRoundTrip(t *testing.T) {
|
|
s := &Set{}
|
|
// A spread of fields: EOF range, both namespaces, two sessions, distinct pids.
|
|
mustAcquire(t, s, Range{Start: 0, End: 99, Type: Write, Sid: 1, Owner: 7, Pid: 10})
|
|
mustAcquire(t, s, Range{Start: 200, End: math.MaxUint64, Type: Read, Sid: 2, Owner: 7, Pid: 20})
|
|
mustAcquire(t, s, Range{Start: 0, End: math.MaxUint64, Type: Write, Sid: 2, Owner: 9, Pid: 30, IsFlock: true})
|
|
|
|
b, err := s.Marshal()
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
got, err := Unmarshal(b)
|
|
if err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(got.Locks(), s.Locks()) {
|
|
t.Fatalf("round-trip mismatch:\n got %+v\nwant %+v", got.Locks(), s.Locks())
|
|
}
|
|
}
|
|
|
|
func TestUnmarshalRejectsMalformed(t *testing.T) {
|
|
// field 1 (locks), wire type 2 (length-delimited) declaring 5 bytes that
|
|
// are not there: a corrupt blob must fail loudly, not read back as empty.
|
|
if _, err := Unmarshal([]byte{0x0A, 0x05}); err == nil {
|
|
t.Fatal("expected an error on malformed input")
|
|
}
|
|
}
|
|
|
|
// A decoded set must behave identically — conflict detection still fires.
|
|
func TestRoundTripPreservesConflict(t *testing.T) {
|
|
s := &Set{}
|
|
mustAcquire(t, s, Range{Start: 0, End: 99, Type: Write, Sid: 1, Owner: 1, Pid: 10})
|
|
|
|
b, err := s.Marshal()
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
decoded, err := Unmarshal(b)
|
|
if err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
if _, granted := decoded.Acquire(Range{Start: 50, End: 149, Type: Write, Sid: 2, Owner: 1, Pid: 20}); granted {
|
|
t.Fatal("decoded set should still report the conflict")
|
|
}
|
|
// Same owner across the wire is still the same owner.
|
|
if _, granted := decoded.Acquire(Range{Start: 0, End: 99, Type: Read, Sid: 1, Owner: 1, Pid: 10}); !granted {
|
|
t.Fatal("decoded set should still recognize the same (sid,owner)")
|
|
}
|
|
}
|