goexpirelock/spinlock.go
2025-04-18 09:47:46 +08:00

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
}
}
}
}