Files
gocryptotrader/common/timedmutex/timed_mutex.go
Ryan O'Hara-Reid 3aad665e90 common/timedmutex: clean and improve (#1486)
* improv. timed mutex

* Add all protection back in and jankyness because races. :'(

* Add intial benchmarkeroos

* Add master benchmarks

* goodness me

* what?

* what again?

* glorious: nits

* just a swaperino instead

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
2024-03-20 16:45:19 +11:00

63 lines
1.7 KiB
Go

package timedmutex
import (
"sync"
"sync/atomic"
"time"
)
// TimedMutex is a blocking mutex which will unlock after a specified time
type TimedMutex struct {
// primary mutex is the main lock that will be unlocked after the duration
primary sync.Mutex
// secondary mutex is used to protect the timer
secondary sync.Mutex
timer *time.Timer
// primed is used to determine if the timer has been started this is
// slightly more performant than checking the timer directly and interacting
// with a RW mutex.
primed atomic.Bool
duration time.Duration
}
// NewTimedMutex creates a new timed mutex with a specified duration
func NewTimedMutex(length time.Duration) *TimedMutex {
return &TimedMutex{duration: length}
}
// LockForDuration will start a timer, lock the mutex, then allow the caller to continue
// After the duration, the mutex will be unlocked
func (t *TimedMutex) LockForDuration() {
t.primary.Lock()
if !t.primed.Swap(true) {
t.secondary.Lock()
t.timer = time.AfterFunc(t.duration, func() { t.primary.Unlock() })
t.secondary.Unlock()
} else {
// Timer C channel is not used with AfterFunc, so no need to drain.
t.secondary.Lock()
t.timer.Reset(t.duration)
t.secondary.Unlock()
}
}
// UnlockIfLocked will unlock the mutex if its currently locked Will return true
// if successfully unlocked
func (t *TimedMutex) UnlockIfLocked() bool {
if !t.primed.Load() {
return false
}
t.secondary.Lock()
wasStoppedByCall := t.timer.Stop()
t.secondary.Unlock()
if !wasStoppedByCall {
// Timer has already fired and the mutex has been unlocked.
// Timer C channel is not used with AfterFunc, so no need to drain.
return false
}
t.primary.Unlock()
return true
}