github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/local-locker_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"context"
    22  	"encoding/hex"
    23  	"fmt"
    24  	"math/rand"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/google/uuid"
    29  	"github.com/minio/minio/internal/dsync"
    30  )
    31  
    32  func TestLocalLockerExpire(t *testing.T) {
    33  	wResources := make([]string, 1000)
    34  	rResources := make([]string, 1000)
    35  	l := newLocker()
    36  	ctx := context.Background()
    37  	for i := range wResources {
    38  		arg := dsync.LockArgs{
    39  			UID:       mustGetUUID(),
    40  			Resources: []string{mustGetUUID()},
    41  			Source:    t.Name(),
    42  			Owner:     "owner",
    43  			Quorum:    0,
    44  		}
    45  		ok, err := l.Lock(ctx, arg)
    46  		if err != nil {
    47  			t.Fatal(err)
    48  		}
    49  		if !ok {
    50  			t.Fatal("did not get write lock")
    51  		}
    52  		wResources[i] = arg.Resources[0]
    53  	}
    54  	for i := range rResources {
    55  		name := mustGetUUID()
    56  		arg := dsync.LockArgs{
    57  			UID:       mustGetUUID(),
    58  			Resources: []string{name},
    59  			Source:    t.Name(),
    60  			Owner:     "owner",
    61  			Quorum:    0,
    62  		}
    63  		ok, err := l.RLock(ctx, arg)
    64  		if err != nil {
    65  			t.Fatal(err)
    66  		}
    67  		if !ok {
    68  			t.Fatal("did not get write lock")
    69  		}
    70  		// RLock twice
    71  		ok, err = l.RLock(ctx, arg)
    72  		if err != nil {
    73  			t.Fatal(err)
    74  		}
    75  		if !ok {
    76  			t.Fatal("did not get write lock")
    77  		}
    78  
    79  		rResources[i] = arg.Resources[0]
    80  	}
    81  	if len(l.lockMap) != len(rResources)+len(wResources) {
    82  		t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), len(rResources), len(wResources))
    83  	}
    84  	if len(l.lockUID) != len(rResources)+len(wResources) {
    85  		t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources), len(wResources))
    86  	}
    87  	// Expire an hour from now, should keep all
    88  	l.expireOldLocks(time.Hour)
    89  	if len(l.lockMap) != len(rResources)+len(wResources) {
    90  		t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), len(rResources), len(wResources))
    91  	}
    92  	if len(l.lockUID) != len(rResources)+len(wResources) {
    93  		t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources), len(wResources))
    94  	}
    95  
    96  	// Expire a minute ago.
    97  	l.expireOldLocks(-time.Minute)
    98  	if len(l.lockMap) != 0 {
    99  		t.Fatalf("after cleanup should be empty, got %d", len(l.lockMap))
   100  	}
   101  	if len(l.lockUID) != 0 {
   102  		t.Fatalf("lockUID len, got %d, want %d", len(l.lockUID), 0)
   103  	}
   104  }
   105  
   106  func TestLocalLockerUnlock(t *testing.T) {
   107  	const n = 1000
   108  	const m = 5
   109  	wResources := make([][m]string, n)
   110  	rResources := make([]string, n)
   111  	wUIDs := make([]string, n)
   112  	rUIDs := make([]string, 0, n*2)
   113  	l := newLocker()
   114  	ctx := context.Background()
   115  	for i := range wResources {
   116  		names := [m]string{}
   117  		for j := range names {
   118  			names[j] = mustGetUUID()
   119  		}
   120  		uid := mustGetUUID()
   121  		arg := dsync.LockArgs{
   122  			UID:       uid,
   123  			Resources: names[:],
   124  			Source:    t.Name(),
   125  			Owner:     "owner",
   126  			Quorum:    0,
   127  		}
   128  		ok, err := l.Lock(ctx, arg)
   129  		if err != nil {
   130  			t.Fatal(err)
   131  		}
   132  		if !ok {
   133  			t.Fatal("did not get write lock")
   134  		}
   135  		wResources[i] = names
   136  		wUIDs[i] = uid
   137  
   138  	}
   139  	for i := range rResources {
   140  		name := mustGetUUID()
   141  		uid := mustGetUUID()
   142  		arg := dsync.LockArgs{
   143  			UID:       uid,
   144  			Resources: []string{name},
   145  			Source:    t.Name(),
   146  			Owner:     "owner",
   147  			Quorum:    0,
   148  		}
   149  		ok, err := l.RLock(ctx, arg)
   150  		if err != nil {
   151  			t.Fatal(err)
   152  		}
   153  		if !ok {
   154  			t.Fatal("did not get write lock")
   155  		}
   156  		rUIDs = append(rUIDs, uid)
   157  
   158  		// RLock twice, different uid
   159  		uid = mustGetUUID()
   160  		arg.UID = uid
   161  		ok, err = l.RLock(ctx, arg)
   162  		if err != nil {
   163  			t.Fatal(err)
   164  		}
   165  		if !ok {
   166  			t.Fatal("did not get write lock")
   167  		}
   168  		rResources[i] = name
   169  		rUIDs = append(rUIDs, uid)
   170  	}
   171  	// Each Lock has m entries
   172  	if len(l.lockMap) != len(rResources)+len(wResources)*m {
   173  		t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), len(rResources), len(wResources)*m)
   174  	}
   175  	// A UID is added for every resource
   176  	if len(l.lockUID) != len(rResources)*2+len(wResources)*m {
   177  		t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources)*2, len(wResources)*m)
   178  	}
   179  	// RUnlock once...
   180  	for i, name := range rResources {
   181  		arg := dsync.LockArgs{
   182  			UID:       rUIDs[i*2],
   183  			Resources: []string{name},
   184  			Source:    t.Name(),
   185  			Owner:     "owner",
   186  			Quorum:    0,
   187  		}
   188  		ok, err := l.RUnlock(ctx, arg)
   189  		if err != nil {
   190  			t.Fatal(err)
   191  		}
   192  		if !ok {
   193  			t.Fatal("did not get write lock")
   194  		}
   195  	}
   196  
   197  	// Each Lock has m entries
   198  	if len(l.lockMap) != len(rResources)+len(wResources)*m {
   199  		t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), len(rResources), len(wResources)*m)
   200  	}
   201  	// A UID is added for every resource.
   202  	// We removed len(rResources) read sources.
   203  	if len(l.lockUID) != len(rResources)+len(wResources)*m {
   204  		t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources), len(wResources)*m)
   205  	}
   206  	// RUnlock again, different uids
   207  	for i, name := range rResources {
   208  		arg := dsync.LockArgs{
   209  			UID:       rUIDs[i*2+1],
   210  			Resources: []string{name},
   211  			Source:    "minio",
   212  			Owner:     "owner",
   213  			Quorum:    0,
   214  		}
   215  		ok, err := l.RUnlock(ctx, arg)
   216  		if err != nil {
   217  			t.Fatal(err)
   218  		}
   219  		if !ok {
   220  			t.Fatal("did not get write lock")
   221  		}
   222  	}
   223  
   224  	// Each Lock has m entries
   225  	if len(l.lockMap) != 0+len(wResources)*m {
   226  		t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), 0, len(wResources)*m)
   227  	}
   228  	// A UID is added for every resource.
   229  	// We removed Add Rlocked entries
   230  	if len(l.lockUID) != len(wResources)*m {
   231  		t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), 0, len(wResources)*m)
   232  	}
   233  
   234  	// Remove write locked
   235  	for i, names := range wResources {
   236  		arg := dsync.LockArgs{
   237  			UID:       wUIDs[i],
   238  			Resources: names[:],
   239  			Source:    "minio",
   240  			Owner:     "owner",
   241  			Quorum:    0,
   242  		}
   243  		ok, err := l.Unlock(ctx, arg)
   244  		if err != nil {
   245  			t.Fatal(err)
   246  		}
   247  		if !ok {
   248  			t.Fatal("did not get write lock")
   249  		}
   250  	}
   251  
   252  	// All should be gone now...
   253  	// Each Lock has m entries
   254  	if len(l.lockMap) != 0 {
   255  		t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), 0, 0)
   256  	}
   257  	if len(l.lockUID) != 0 {
   258  		t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), 0, 0)
   259  	}
   260  }
   261  
   262  func Test_localLocker_expireOldLocksExpire(t *testing.T) {
   263  	rng := rand.New(rand.NewSource(0))
   264  	// Numbers of unique locks
   265  	for _, locks := range []int{100, 1000, 1e6} {
   266  		if testing.Short() && locks > 100 {
   267  			continue
   268  		}
   269  		t.Run(fmt.Sprintf("%d-locks", locks), func(t *testing.T) {
   270  			// Number of readers per lock...
   271  			for _, readers := range []int{1, 10, 100} {
   272  				if locks > 1000 && readers > 1 {
   273  					continue
   274  				}
   275  				if testing.Short() && readers > 10 {
   276  					continue
   277  				}
   278  				t.Run(fmt.Sprintf("%d-read", readers), func(t *testing.T) {
   279  					l := newLocker()
   280  					for i := 0; i < locks; i++ {
   281  						var tmp [16]byte
   282  						rng.Read(tmp[:])
   283  						res := []string{hex.EncodeToString(tmp[:])}
   284  
   285  						for i := 0; i < readers; i++ {
   286  							rng.Read(tmp[:])
   287  							ok, err := l.RLock(context.Background(), dsync.LockArgs{
   288  								UID:       uuid.NewString(),
   289  								Resources: res,
   290  								Source:    hex.EncodeToString(tmp[:8]),
   291  								Owner:     hex.EncodeToString(tmp[8:]),
   292  								Quorum:    0,
   293  							})
   294  							if !ok || err != nil {
   295  								t.Fatal("failed:", err, ok)
   296  							}
   297  						}
   298  					}
   299  					start := time.Now()
   300  					l.expireOldLocks(time.Hour)
   301  					t.Logf("Scan Took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap))
   302  					if len(l.lockMap) != locks {
   303  						t.Fatalf("objects deleted, want %d != got %d", locks, len(l.lockMap))
   304  					}
   305  					if len(l.lockUID) != locks*readers {
   306  						t.Fatalf("objects deleted, want %d != got %d", locks*readers, len(l.lockUID))
   307  					}
   308  
   309  					// Expire 50%
   310  					expired := time.Now().Add(-time.Hour * 2)
   311  					for _, v := range l.lockMap {
   312  						for i := range v {
   313  							if rng.Intn(2) == 0 {
   314  								v[i].TimeLastRefresh = expired
   315  							}
   316  						}
   317  					}
   318  					start = time.Now()
   319  					l.expireOldLocks(time.Hour)
   320  					t.Logf("Expire 50%% took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap))
   321  
   322  					if len(l.lockUID) == locks*readers {
   323  						t.Fatalf("objects uids all remain, unlikely")
   324  					}
   325  					if len(l.lockMap) == 0 {
   326  						t.Fatalf("objects all deleted, 0 remains")
   327  					}
   328  					if len(l.lockUID) == 0 {
   329  						t.Fatalf("objects uids all deleted, 0 remains")
   330  					}
   331  
   332  					start = time.Now()
   333  					l.expireOldLocks(-time.Minute)
   334  					t.Logf("Expire rest took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap))
   335  
   336  					if len(l.lockMap) != 0 {
   337  						t.Fatalf("objects not deleted, want %d != got %d", 0, len(l.lockMap))
   338  					}
   339  					if len(l.lockUID) != 0 {
   340  						t.Fatalf("objects not deleted, want %d != got %d", 0, len(l.lockUID))
   341  					}
   342  				})
   343  			}
   344  		})
   345  	}
   346  }
   347  
   348  func Test_localLocker_RUnlock(t *testing.T) {
   349  	rng := rand.New(rand.NewSource(0))
   350  	// Numbers of unique locks
   351  	for _, locks := range []int{1, 100, 1000, 1e6} {
   352  		if testing.Short() && locks > 100 {
   353  			continue
   354  		}
   355  		t.Run(fmt.Sprintf("%d-locks", locks), func(t *testing.T) {
   356  			// Number of readers per lock...
   357  			for _, readers := range []int{1, 10, 100} {
   358  				if locks > 1000 && readers > 1 {
   359  					continue
   360  				}
   361  				if testing.Short() && readers > 10 {
   362  					continue
   363  				}
   364  				t.Run(fmt.Sprintf("%d-read", readers), func(t *testing.T) {
   365  					l := newLocker()
   366  					for i := 0; i < locks; i++ {
   367  						var tmp [16]byte
   368  						rng.Read(tmp[:])
   369  						res := []string{hex.EncodeToString(tmp[:])}
   370  
   371  						for i := 0; i < readers; i++ {
   372  							rng.Read(tmp[:])
   373  							ok, err := l.RLock(context.Background(), dsync.LockArgs{
   374  								UID:       uuid.NewString(),
   375  								Resources: res,
   376  								Source:    hex.EncodeToString(tmp[:8]),
   377  								Owner:     hex.EncodeToString(tmp[8:]),
   378  								Quorum:    0,
   379  							})
   380  							if !ok || err != nil {
   381  								t.Fatal("failed:", err, ok)
   382  							}
   383  						}
   384  					}
   385  
   386  					// Expire 50%
   387  					toUnLock := make([]dsync.LockArgs, 0, locks*readers)
   388  					for k, v := range l.lockMap {
   389  						for _, lock := range v {
   390  							if rng.Intn(2) == 0 {
   391  								toUnLock = append(toUnLock, dsync.LockArgs{Resources: []string{k}, UID: lock.UID})
   392  							}
   393  						}
   394  					}
   395  					start := time.Now()
   396  					for _, lock := range toUnLock {
   397  						ok, err := l.ForceUnlock(context.Background(), lock)
   398  						if err != nil || !ok {
   399  							t.Fatal(err)
   400  						}
   401  					}
   402  					t.Logf("Expire 50%% took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap))
   403  
   404  					if len(l.lockUID) == locks*readers {
   405  						t.Fatalf("objects uids all remain, unlikely")
   406  					}
   407  					if len(l.lockMap) == 0 && locks > 10 {
   408  						t.Fatalf("objects all deleted, 0 remains")
   409  					}
   410  					if len(l.lockUID) != locks*readers-len(toUnLock) {
   411  						t.Fatalf("want %d objects uids all deleted, %d remains", len(l.lockUID), locks*readers-len(toUnLock))
   412  					}
   413  
   414  					toUnLock = toUnLock[:0]
   415  					for k, v := range l.lockMap {
   416  						for _, lock := range v {
   417  							toUnLock = append(toUnLock, dsync.LockArgs{Resources: []string{k}, UID: lock.UID, Owner: lock.Owner})
   418  						}
   419  					}
   420  					start = time.Now()
   421  					for _, lock := range toUnLock {
   422  						ok, err := l.RUnlock(context.TODO(), lock)
   423  						if err != nil || !ok {
   424  							t.Fatal(err)
   425  						}
   426  					}
   427  					t.Logf("Expire rest took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap))
   428  
   429  					if len(l.lockMap) != 0 {
   430  						t.Fatalf("objects not deleted, want %d != got %d", 0, len(l.lockMap))
   431  					}
   432  					if len(l.lockUID) != 0 {
   433  						t.Fatalf("objects not deleted, want %d != got %d", 0, len(l.lockUID))
   434  					}
   435  				})
   436  			}
   437  		})
   438  	}
   439  }