github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/lsync/lrwmutex.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 lsync
    19  
    20  import (
    21  	"context"
    22  	"math"
    23  	"math/rand"
    24  	"sync"
    25  	"time"
    26  )
    27  
    28  // A LRWMutex is a mutual exclusion lock with timeouts.
    29  type LRWMutex struct {
    30  	id          string
    31  	source      string
    32  	isWriteLock bool
    33  	ref         int
    34  	mu          sync.Mutex // Mutex to prevent multiple simultaneous locks
    35  }
    36  
    37  // NewLRWMutex - initializes a new lsync RW mutex.
    38  func NewLRWMutex() *LRWMutex {
    39  	return &LRWMutex{}
    40  }
    41  
    42  // Lock holds a write lock on lm.
    43  //
    44  // If the lock is already in use, the calling go routine
    45  // blocks until the mutex is available.
    46  func (lm *LRWMutex) Lock() {
    47  	const isWriteLock = true
    48  	lm.lockLoop(context.Background(), lm.id, lm.source, math.MaxInt64, isWriteLock)
    49  }
    50  
    51  // GetLock tries to get a write lock on lm before the timeout occurs.
    52  func (lm *LRWMutex) GetLock(ctx context.Context, id string, source string, timeout time.Duration) (locked bool) {
    53  	const isWriteLock = true
    54  	return lm.lockLoop(ctx, id, source, timeout, isWriteLock)
    55  }
    56  
    57  // RLock holds a read lock on lm.
    58  //
    59  // If one or more read lock are already in use, it will grant another lock.
    60  // Otherwise the calling go routine blocks until the mutex is available.
    61  func (lm *LRWMutex) RLock() {
    62  	const isWriteLock = false
    63  	lm.lockLoop(context.Background(), lm.id, lm.source, 1<<63-1, isWriteLock)
    64  }
    65  
    66  // GetRLock tries to get a read lock on lm before the timeout occurs.
    67  func (lm *LRWMutex) GetRLock(ctx context.Context, id string, source string, timeout time.Duration) (locked bool) {
    68  	const isWriteLock = false
    69  	return lm.lockLoop(ctx, id, source, timeout, isWriteLock)
    70  }
    71  
    72  func (lm *LRWMutex) lock(id, source string, isWriteLock bool) (locked bool) {
    73  	lm.mu.Lock()
    74  	defer lm.mu.Unlock()
    75  
    76  	lm.id = id
    77  	lm.source = source
    78  	if isWriteLock {
    79  		if lm.ref == 0 && !lm.isWriteLock {
    80  			lm.ref = 1
    81  			lm.isWriteLock = true
    82  			locked = true
    83  		}
    84  	} else {
    85  		if !lm.isWriteLock {
    86  			lm.ref++
    87  			locked = true
    88  		}
    89  	}
    90  
    91  	return locked
    92  }
    93  
    94  const (
    95  	lockRetryInterval = 50 * time.Millisecond
    96  )
    97  
    98  // lockLoop will acquire either a read or a write lock
    99  //
   100  // The call will block until the lock is granted using a built-in
   101  // timing randomized back-off algorithm to try again until successful
   102  func (lm *LRWMutex) lockLoop(ctx context.Context, id, source string, timeout time.Duration, isWriteLock bool) (locked bool) {
   103  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   104  
   105  	retryCtx, cancel := context.WithTimeout(ctx, timeout)
   106  	defer cancel()
   107  
   108  	for {
   109  		select {
   110  		case <-retryCtx.Done():
   111  			// Caller context canceled or we timedout,
   112  			// return false anyways for both situations.
   113  			return false
   114  		default:
   115  			if lm.lock(id, source, isWriteLock) {
   116  				return true
   117  			}
   118  			time.Sleep(time.Duration(r.Float64() * float64(lockRetryInterval)))
   119  		}
   120  	}
   121  }
   122  
   123  // Unlock unlocks the write lock.
   124  //
   125  // It is a run-time error if lm is not locked on entry to Unlock.
   126  func (lm *LRWMutex) Unlock() {
   127  	isWriteLock := true
   128  	success := lm.unlock(isWriteLock)
   129  	if !success {
   130  		panic("Trying to Unlock() while no Lock() is active")
   131  	}
   132  }
   133  
   134  // RUnlock releases a read lock held on lm.
   135  //
   136  // It is a run-time error if lm is not locked on entry to RUnlock.
   137  func (lm *LRWMutex) RUnlock() {
   138  	isWriteLock := false
   139  	success := lm.unlock(isWriteLock)
   140  	if !success {
   141  		panic("Trying to RUnlock() while no RLock() is active")
   142  	}
   143  }
   144  
   145  func (lm *LRWMutex) unlock(isWriteLock bool) (unlocked bool) {
   146  	lm.mu.Lock()
   147  	defer lm.mu.Unlock()
   148  
   149  	// Try to release lock.
   150  	if isWriteLock {
   151  		if lm.isWriteLock && lm.ref == 1 {
   152  			lm.ref = 0
   153  			lm.isWriteLock = false
   154  			unlocked = true
   155  		}
   156  	} else {
   157  		if !lm.isWriteLock {
   158  			if lm.ref > 0 {
   159  				lm.ref--
   160  				unlocked = true
   161  			}
   162  		}
   163  	}
   164  
   165  	return unlocked
   166  }
   167  
   168  // ForceUnlock will forcefully clear a write or read lock.
   169  func (lm *LRWMutex) ForceUnlock() {
   170  	lm.mu.Lock()
   171  	defer lm.mu.Unlock()
   172  
   173  	lm.ref = 0
   174  	lm.isWriteLock = false
   175  }
   176  
   177  // DRLocker returns a sync.Locker interface that implements
   178  // the Lock and Unlock methods by calling drw.RLock and drw.RUnlock.
   179  func (lm *LRWMutex) DRLocker() sync.Locker {
   180  	return (*drlocker)(lm)
   181  }
   182  
   183  type drlocker LRWMutex
   184  
   185  func (dr *drlocker) Lock()   { (*LRWMutex)(dr).RLock() }
   186  func (dr *drlocker) Unlock() { (*LRWMutex)(dr).RUnlock() }