mirror of
https://github.com/pgsty/minio.git
synced 2026-11-10 09:35:31 +03:00
test(pools): make the copy and bulk lock tests race-free and stricter
The implementation review of the lock fixes found test flaws, which are corrected here; the production behavior is unchanged. - The alias and merge tests replaced globalOperationTimeout while background healing reads it, a data race under -race. They now bound each request with its own context instead. - The alias test now holds the cleaned name's write lock while the copy runs, so it proves the names are checked before anything is locked; it checks both objects before any later write, and covers a/./x as a destination. - The merge test picks aliases that hash to the clean name's set and, where sets have their own lockers, ones that do not, and requires the alias's own table to hold a read lock: readers admitted, writers excluded. - Release polling creates a fresh lock instance for every probe. - The getNSLocks event hook and the bulk delete group limit are atomic, so tests can set them while other goroutines read them. Without the name check and the resource merge, both tests now fail in every lock layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -35,13 +35,14 @@ import (
|
||||
func nsLocksEvents(t *testing.T) chan string {
|
||||
t.Helper()
|
||||
events := make(chan string, 256)
|
||||
getNSLocksEvent = func(event string, index int) {
|
||||
f := func(event string, index int) {
|
||||
select {
|
||||
case events <- fmt.Sprintf("%s:%d", event, index):
|
||||
default:
|
||||
}
|
||||
}
|
||||
t.Cleanup(func() { getNSLocksEvent = nil })
|
||||
getNSLocksEvent.Store(&f)
|
||||
t.Cleanup(func() { getNSLocksEvent.Store(nil) })
|
||||
return events
|
||||
}
|
||||
|
||||
@@ -61,6 +62,20 @@ func waitNSLocksEvent(t *testing.T, events chan string, want string) {
|
||||
}
|
||||
}
|
||||
|
||||
// ldFreeFresh waits, bounded, until a fresh instance from lock can take the
|
||||
// lock: a distributed release can still be on its way after it was asked
|
||||
// for, and a late unlock of an earlier instance never releases a new one.
|
||||
func ldFreeFresh(t *testing.T, lock func() RWLocker, write bool) bool {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for !ldTry(t, lock(), write) {
|
||||
if time.Now().After(deadline) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type nsLocksResult struct {
|
||||
unlock func()
|
||||
err error
|
||||
@@ -104,7 +119,7 @@ func TestGetNSLocksWaitsHoldingNothing(t *testing.T) {
|
||||
waitNSLocksEvent(t, events, "idle-wait:1")
|
||||
// The event says that a was released; a distributed unlock may
|
||||
// still be on its way.
|
||||
if !ldFree(t, lock("a"), true) {
|
||||
if !ldFreeFresh(t, func() RWLocker { return lock("a") }, true) {
|
||||
t.Fatal("a is not released while b is waited for")
|
||||
}
|
||||
releaseA := ldHoldLock(t, lock("a"))
|
||||
@@ -137,7 +152,7 @@ func TestGetNSLocksWaitsHoldingNothing(t *testing.T) {
|
||||
t.Error("not both locks held")
|
||||
}
|
||||
res.unlock()
|
||||
if !ldFree(t, lock("a"), true) || !ldFree(t, lock("b"), true) {
|
||||
if !ldFreeFresh(t, func() RWLocker { return lock("a") }, true) || !ldFreeFresh(t, func() RWLocker { return lock("b") }, true) {
|
||||
t.Error("locks left behind")
|
||||
}
|
||||
})
|
||||
@@ -167,7 +182,7 @@ func TestGetNSLocksIdleWaitCancel(t *testing.T) {
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("a canceled idle wait did not return")
|
||||
}
|
||||
if !ldFree(t, lock("a"), true) {
|
||||
if !ldFreeFresh(t, func() RWLocker { return lock("a") }, true) {
|
||||
t.Error("a left behind")
|
||||
}
|
||||
})
|
||||
@@ -199,7 +214,7 @@ func TestGetNSLocksIdleWaitDeadline(t *testing.T) {
|
||||
case <-time.After(timeout + 5*time.Second):
|
||||
t.Fatal("the idle wait outlived the deadline")
|
||||
}
|
||||
if !ldFree(t, lock("a"), true) {
|
||||
if !ldFreeFresh(t, func() RWLocker { return lock("a") }, true) {
|
||||
t.Error("a left behind")
|
||||
}
|
||||
})
|
||||
@@ -264,7 +279,7 @@ func TestLockDomainCopyDoesNotStarveDestination(t *testing.T) {
|
||||
done <- ldCopyRequest(t.Context(), t, router, bucket, src, dst, nil).Code
|
||||
}()
|
||||
waitNSLocksEvent(t, events, "idle-wait:1")
|
||||
if !ldFree(t, z.NewObjectNSLock(bucket, dst), true) {
|
||||
if !ldFreeFresh(t, func() RWLocker { return z.NewObjectNSLock(bucket, dst) }, true) {
|
||||
t.Fatal("the destination is not released while the source is waited for")
|
||||
}
|
||||
for range 3 {
|
||||
|
||||
Reference in New Issue
Block a user