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