storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/lsync/lrwmutex.go (about)

     1  /*
     2   * Minio Cloud Storage, (C) 2017 Minio, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package lsync
    18  
    19  import (
    20  	"context"
    21  	"math"
    22  	"math/rand"
    23  	"sync"
    24  	"time"
    25  )
    26  
    27  // A LRWMutex is a mutual exclusion lock with timeouts.
    28  type LRWMutex struct {
    29  	id          string
    30  	source      string
    31  	isWriteLock bool
    32  	ref         int
    33  	mu          sync.Mutex // Mutex to prevent multiple simultaneous locks
    34  }
    35  
    36  // NewLRWMutex - initializes a new lsync RW mutex.
    37  func NewLRWMutex() *LRWMutex {
    38  	return &LRWMutex{}
    39  }
    40  
    41  // Lock holds a write lock on lm.
    42  //
    43  // If the lock is already in use, the calling go routine
    44  // blocks until the mutex is available.
    45  func (lm *LRWMutex) Lock() {
    46  
    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  
    54  	const isWriteLock = true
    55  	return lm.lockLoop(ctx, id, source, timeout, isWriteLock)
    56  }
    57  
    58  // RLock holds a read lock on lm.
    59  //
    60  // If one or more read lock are already in use, it will grant another lock.
    61  // Otherwise the calling go routine blocks until the mutex is available.
    62  func (lm *LRWMutex) RLock() {
    63  
    64  	const isWriteLock = false
    65  	lm.lockLoop(context.Background(), lm.id, lm.source, 1<<63-1, isWriteLock)
    66  }
    67  
    68  // GetRLock tries to get a read lock on lm before the timeout occurs.
    69  func (lm *LRWMutex) GetRLock(ctx context.Context, id string, source string, timeout time.Duration) (locked bool) {
    70  
    71  	const isWriteLock = false
    72  	return lm.lockLoop(ctx, id, source, timeout, isWriteLock)
    73  }
    74  
    75  func (lm *LRWMutex) lock(id, source string, isWriteLock bool) (locked bool) {
    76  	lm.mu.Lock()
    77  	defer lm.mu.Unlock()
    78  
    79  	lm.id = id
    80  	lm.source = source
    81  	if isWriteLock {
    82  		if lm.ref == 0 && !lm.isWriteLock {
    83  			lm.ref = 1
    84  			lm.isWriteLock = true
    85  			locked = true
    86  		}
    87  	} else {
    88  		if !lm.isWriteLock {
    89  			lm.ref++
    90  			locked = true
    91  		}
    92  	}
    93  
    94  	return locked
    95  }
    96  
    97  const (
    98  	lockRetryInterval = 50 * time.Millisecond
    99  )
   100  
   101  // lockLoop will acquire either a read or a write lock
   102  //
   103  // The call will block until the lock is granted using a built-in
   104  // timing randomized back-off algorithm to try again until successful
   105  func (lm *LRWMutex) lockLoop(ctx context.Context, id, source string, timeout time.Duration, isWriteLock bool) (locked bool) {
   106  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   107  
   108  	retryCtx, cancel := context.WithTimeout(ctx, timeout)
   109  	defer cancel()
   110  
   111  	for {
   112  		select {
   113  		case <-retryCtx.Done():
   114  			// Caller context canceled or we timedout,
   115  			// return false anyways for both situations.
   116  			return false
   117  		default:
   118  			if lm.lock(id, source, isWriteLock) {
   119  				return true
   120  			}
   121  			time.Sleep(time.Duration(r.Float64() * float64(lockRetryInterval)))
   122  		}
   123  	}
   124  }
   125  
   126  // Unlock unlocks the write lock.
   127  //
   128  // It is a run-time error if lm is not locked on entry to Unlock.
   129  func (lm *LRWMutex) Unlock() {
   130  
   131  	isWriteLock := true
   132  	success := lm.unlock(isWriteLock)
   133  	if !success {
   134  		panic("Trying to Unlock() while no Lock() is active")
   135  	}
   136  }
   137  
   138  // RUnlock releases a read lock held on lm.
   139  //
   140  // It is a run-time error if lm is not locked on entry to RUnlock.
   141  func (lm *LRWMutex) RUnlock() {
   142  
   143  	isWriteLock := false
   144  	success := lm.unlock(isWriteLock)
   145  	if !success {
   146  		panic("Trying to RUnlock() while no RLock() is active")
   147  	}
   148  }
   149  
   150  func (lm *LRWMutex) unlock(isWriteLock bool) (unlocked bool) {
   151  	lm.mu.Lock()
   152  	defer lm.mu.Unlock()
   153  
   154  	// Try to release lock.
   155  	if isWriteLock {
   156  		if lm.isWriteLock && lm.ref == 1 {
   157  			lm.ref = 0
   158  			lm.isWriteLock = false
   159  			unlocked = true
   160  		}
   161  	} else {
   162  		if !lm.isWriteLock {
   163  			if lm.ref > 0 {
   164  				lm.ref--
   165  				unlocked = true
   166  			}
   167  		}
   168  	}
   169  
   170  	return unlocked
   171  }
   172  
   173  // ForceUnlock will forcefully clear a write or read lock.
   174  func (lm *LRWMutex) ForceUnlock() {
   175  	lm.mu.Lock()
   176  	defer lm.mu.Unlock()
   177  
   178  	lm.ref = 0
   179  	lm.isWriteLock = false
   180  
   181  }
   182  
   183  // DRLocker returns a sync.Locker interface that implements
   184  // the Lock and Unlock methods by calling drw.RLock and drw.RUnlock.
   185  func (lm *LRWMutex) DRLocker() sync.Locker {
   186  	return (*drlocker)(lm)
   187  }
   188  
   189  type drlocker LRWMutex
   190  
   191  func (dr *drlocker) Lock()   { (*LRWMutex)(dr).RLock() }
   192  func (dr *drlocker) Unlock() { (*LRWMutex)(dr).RUnlock() }