81 lines
1.2 KiB
Go
81 lines
1.2 KiB
Go
package expirelock
|
|
|
|
import (
|
|
"errors"
|
|
"expirelock/os"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
type Spin struct {
|
|
token atomic.Value
|
|
mu sync.Mutex
|
|
expire time.Time
|
|
locked bool
|
|
}
|
|
|
|
func NewSpin() *Spin {
|
|
return &Spin{}
|
|
}
|
|
|
|
func (s *Spin) tryLock(ttl time.Duration) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
if !s.locked || now.After(s.expire) {
|
|
s.locked = true
|
|
s.token.Store(os.GetCurrentProcessAndGogroutineIDStr())
|
|
s.expire = now.Add(ttl)
|
|
|
|
return nil
|
|
}
|
|
|
|
return errors.New("already locked")
|
|
}
|
|
|
|
func (s *Spin) Unlock() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
id, _ := s.token.Load().(string)
|
|
if id != os.GetCurrentProcessAndGogroutineIDStr() {
|
|
return errors.New("lock does not belong to this process")
|
|
}
|
|
|
|
if !s.locked || time.Now().After(s.expire) {
|
|
return errors.New("unlock already locked")
|
|
}
|
|
|
|
s.locked = false
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Spin) Lock(ttl time.Duration) error {
|
|
err := s.tryLock(ttl)
|
|
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
if ttl <= 0 {
|
|
return err
|
|
}
|
|
|
|
ticker := time.NewTicker(20 * time.Millisecond)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-time.After(ttl):
|
|
return errors.New("lock timeout")
|
|
case <-ticker.C:
|
|
if err := s.tryLock(ttl); err == nil {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|