github.com/decred/dcrlnd@v0.7.6/multimutex/hash_mutex.go (about)

     1  package multimutex
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/decred/dcrlnd/lntypes"
     8  )
     9  
    10  // HashMutex is a struct that keeps track of a set of mutexes with a given hash.
    11  // It can be used for making sure only one goroutine gets given the mutex per
    12  // hash.
    13  type HashMutex struct {
    14  	// mutexes is a map of hashes to a cntMutex. The cntMutex for
    15  	// a given hash will hold the mutex to be used by all
    16  	// callers requesting access for the hash, in addition to
    17  	// the count of callers.
    18  	mutexes map[lntypes.Hash]*cntMutex
    19  
    20  	// mapMtx is used to give synchronize concurrent access
    21  	// to the mutexes map.
    22  	mapMtx sync.Mutex
    23  }
    24  
    25  // NewHashMutex creates a new Mutex.
    26  func NewHashMutex() *HashMutex {
    27  	return &HashMutex{
    28  		mutexes: make(map[lntypes.Hash]*cntMutex),
    29  	}
    30  }
    31  
    32  // Lock locks the mutex by the given hash. If the mutex is already
    33  // locked by this hash, Lock blocks until the mutex is available.
    34  func (c *HashMutex) Lock(hash lntypes.Hash) {
    35  	c.mapMtx.Lock()
    36  	mtx, ok := c.mutexes[hash]
    37  	if ok {
    38  		// If the mutex already existed in the map, we
    39  		// increment its counter, to indicate that there
    40  		// now is one more goroutine waiting for it.
    41  		mtx.cnt++
    42  	} else {
    43  		// If it was not in the map, it means no other
    44  		// goroutine has locked the mutex for this hash,
    45  		// and we can create a new mutex with count 1
    46  		// and add it to the map.
    47  		mtx = &cntMutex{
    48  			cnt: 1,
    49  		}
    50  		c.mutexes[hash] = mtx
    51  	}
    52  	c.mapMtx.Unlock()
    53  
    54  	// Acquire the mutex for this hash.
    55  	mtx.Lock()
    56  }
    57  
    58  // Unlock unlocks the mutex by the given hash. It is a run-time
    59  // error if the mutex is not locked by the hash on entry to Unlock.
    60  func (c *HashMutex) Unlock(hash lntypes.Hash) {
    61  	// Since we are done with all the work for this
    62  	// update, we update the map to reflect that.
    63  	c.mapMtx.Lock()
    64  
    65  	mtx, ok := c.mutexes[hash]
    66  	if !ok {
    67  		// The mutex not existing in the map means
    68  		// an unlock for an hash not currently locked
    69  		// was attempted.
    70  		panic(fmt.Sprintf("double unlock for hash %v",
    71  			hash))
    72  	}
    73  
    74  	// Decrement the counter. If the count goes to
    75  	// zero, it means this caller was the last one
    76  	// to wait for the mutex, and we can delete it
    77  	// from the map. We can do this safely since we
    78  	// are under the mapMtx, meaning that all other
    79  	// goroutines waiting for the mutex already
    80  	// have incremented it, or will create a new
    81  	// mutex when they get the mapMtx.
    82  	mtx.cnt--
    83  	if mtx.cnt == 0 {
    84  		delete(c.mutexes, hash)
    85  	}
    86  	c.mapMtx.Unlock()
    87  
    88  	// Unlock the mutex for this hash.
    89  	mtx.Unlock()
    90  }