codeberg.org/gruf/go-mutexes@v1.5.0/muctx/ctx.go (about)

     1  package muctx
     2  
     3  import (
     4  	"context"
     5  	"sync/atomic"
     6  	_ "unsafe"
     7  
     8  	"codeberg.org/gruf/go-mutexes"
     9  )
    10  
    11  // Lock represents a locked key in a mutex map,
    12  // normally only returned as an unlock function
    13  // but wrapped here to provide a storable type
    14  // with metadata for context values.
    15  type Lock struct {
    16  	unlk func()
    17  	lock *uint32
    18  	ltyp uint8
    19  }
    20  
    21  // RLockCtx will check given context for rlock, else acquire an rlock on the mutex at key in the map.
    22  func RLockCtx(mm *mutexes.MutexMap, ctx context.Context, key string) (child context.Context, lock *Lock) {
    23  	return lockCtx(mm, ctx, key, lockTypeRead)
    24  }
    25  
    26  // LockCtx will check given context for lock, else acquire a lock on the mutex at key in the map.
    27  func LockCtx(mm *mutexes.MutexMap, ctx context.Context, key string) (child context.Context, lock *Lock) {
    28  	return lockCtx(mm, ctx, key, lockTypeWrite)
    29  }
    30  
    31  func lockCtx(mm *mutexes.MutexMap, ctx context.Context, key string, lt uint8) (context.Context, *Lock) {
    32  	type ctxkey string
    33  
    34  	// Look for an existing Lock{} with key.
    35  	lock, ok := ctx.Value(ctxkey(key)).(*Lock)
    36  	if ok && lock.Locked() {
    37  		if lock.ltyp&lt == 0 {
    38  			panic("called (r)lock on ctx with different lock type for same key - deadlock!")
    39  		}
    40  		return ctx, lock.Clone()
    41  	}
    42  
    43  	// Alloc new Lock.
    44  	lock = new(Lock)
    45  	lock.lock = new(uint32)
    46  	*lock.lock = 1 // locked
    47  	lock.ltyp = lt
    48  
    49  	// Acquire mutex map lock for key.
    50  	lock.unlk = mutexmap_lock(mm, key, lt)
    51  
    52  	// Wrap parent context with our LockCtx value.
    53  	child := context.WithValue(ctx, ctxkey(key), lock)
    54  	return child, lock
    55  }
    56  
    57  // Locked returns whether Lock is currently locked.
    58  func (ctx *Lock) Locked() bool {
    59  	return atomic.LoadUint32(ctx.lock) == 1
    60  }
    61  
    62  // Unlock will unlock the given Lock (only if it is not a read-only).
    63  func (ctx *Lock) Unlock() bool {
    64  	if ctx.unlk != nil &&
    65  		atomic.CompareAndSwapUint32(ctx.lock, 1, 0) {
    66  		ctx.unlk()
    67  		return true
    68  	}
    69  	return false
    70  }
    71  
    72  // Clone returns a read-only clone of Lock.
    73  func (ctx *Lock) Clone() *Lock {
    74  	return &Lock{
    75  		lock: ctx.lock,
    76  		ltyp: ctx.ltyp,
    77  	}
    78  }
    79  
    80  // IsRLock returns whether this Lock is a read lock.
    81  func (ctx *Lock) IsRLock() bool {
    82  	return ctx.ltyp&lockTypeRead != 0
    83  }
    84  
    85  // IsLock returns whether this Lock is a write lock.
    86  func (ctx *Lock) IsLock() bool {
    87  	return ctx.ltyp&lockTypeWrite != 0
    88  }
    89  
    90  const (
    91  	// possible lock types.
    92  	lockTypeRead  = uint8(1) << 0
    93  	lockTypeWrite = uint8(1) << 1
    94  )
    95  
    96  //go:linkname mutexmap_lock codeberg.org/gruf/go-mutexes.(*MutexMap).lock
    97  func mutexmap_lock(mm *mutexes.MutexMap, key string, lt uint8) func()