github.com/vmware/govmomi@v0.51.0/simulator/internal/object_lock.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package internal
     6  
     7  import (
     8  	"fmt"
     9  	"sync"
    10  )
    11  
    12  // SharedLockingContext is used to identify when locks can be shared. In
    13  // practice, simulator code uses the simulator.Context for a request, but in
    14  // principle this could be anything.
    15  type SharedLockingContext any
    16  
    17  // ObjectLock implements a basic "reference-counted" mutex, where a single
    18  // SharedLockingContext can "share" the lock across code paths or child tasks.
    19  type ObjectLock struct {
    20  	lock sync.Locker
    21  
    22  	stateLock sync.Mutex
    23  	heldBy    SharedLockingContext
    24  	count     int64
    25  }
    26  
    27  // NewObjectLock creates a new ObjectLock. Pass new(sync.Mutex) if you don't
    28  // have a custom sync.Locker.
    29  func NewObjectLock(lock sync.Locker) *ObjectLock {
    30  	return &ObjectLock{
    31  		lock: lock,
    32  	}
    33  }
    34  
    35  // try returns true if the lock has been acquired; false otherwise
    36  func (l *ObjectLock) try(onBehalfOf SharedLockingContext) bool {
    37  	l.stateLock.Lock()
    38  	defer l.stateLock.Unlock()
    39  
    40  	if l.heldBy == onBehalfOf {
    41  		l.count = l.count + 1
    42  		return true
    43  	}
    44  
    45  	if l.heldBy == nil {
    46  		// we expect no contention for this lock (unless the object has a custom Locker)
    47  		l.lock.Lock()
    48  		l.count = 1
    49  		l.heldBy = onBehalfOf
    50  		return true
    51  	}
    52  
    53  	return false
    54  }
    55  
    56  // wait returns when there's a chance that try() might succeed.
    57  // It is intended to be better than busy-waiting or sleeping.
    58  func (l *ObjectLock) wait() {
    59  	l.lock.Lock()
    60  	l.lock.Unlock()
    61  }
    62  
    63  // Release decrements the reference count. The caller should pass their
    64  // context, which is used to sanity check that the Unlock() call is valid. If
    65  // this is the last reference to the lock for this SharedLockingContext, the lock
    66  // is Unlocked and can be acquired by another SharedLockingContext.
    67  func (l *ObjectLock) Release(onBehalfOf SharedLockingContext) {
    68  	l.stateLock.Lock()
    69  	defer l.stateLock.Unlock()
    70  	if l.heldBy != onBehalfOf {
    71  		panic(fmt.Sprintf("Attempt to unlock on behalf of %#v, but is held by %#v", onBehalfOf, l.heldBy))
    72  	}
    73  	l.count = l.count - 1
    74  	if l.count == 0 {
    75  		l.heldBy = nil
    76  		l.lock.Unlock()
    77  	}
    78  }
    79  
    80  // Acquire blocks until it can acquire the lock for onBehalfOf
    81  func (l *ObjectLock) Acquire(onBehalfOf SharedLockingContext) {
    82  	acquired := false
    83  	for !acquired {
    84  		if l.try(onBehalfOf) {
    85  			return
    86  		} else {
    87  			l.wait()
    88  		}
    89  	}
    90  }