github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/lock/lock.go (about) 1 package lock 2 3 import ( 4 "sync" 5 "time" 6 7 "github.com/cozy/cozy-stack/pkg/prefixer" 8 "github.com/redis/go-redis/v9" 9 ) 10 11 // Getter returns a lock on a resource matching the given `name`. 12 type Getter interface { 13 // ReadWrite returns the read/write lock for the given name. 14 // By convention, the name should be prefixed by the instance domain on which 15 // it applies, then a slash and the package name (ie alice.example.net/vfs). 16 ReadWrite(db prefixer.Prefixer, name string) ErrorRWLocker 17 18 // LongOperation returns a lock suitable for long operations. It will refresh 19 // the lock in redis to avoid its automatic expiration. 20 LongOperation(db prefixer.Prefixer, name string) ErrorLocker 21 } 22 23 func New(client redis.UniversalClient) Getter { 24 if client == nil { 25 return NewInMemory() 26 } 27 28 return NewRedisLockGetter(client) 29 } 30 31 // An ErrorLocker is a locker which can fail (returns an error) 32 type ErrorLocker interface { 33 Lock() error 34 Unlock() 35 } 36 37 // ErrorRWLocker is the interface for a RWLock as inspired by RWMutex 38 type ErrorRWLocker interface { 39 ErrorLocker 40 RLock() error 41 RUnlock() 42 } 43 44 type longOperationLocker interface { 45 ErrorLocker 46 Extend() 47 } 48 49 type longOperation struct { 50 lock longOperationLocker 51 mu sync.Mutex 52 tick *time.Ticker 53 timeout time.Duration 54 } 55 56 func (l *longOperation) Lock() error { 57 if err := l.lock.Lock(); err != nil { 58 return err 59 } 60 l.tick = time.NewTicker(l.timeout / 3) 61 go func() { 62 defer l.mu.Unlock() 63 for { 64 l.mu.Lock() 65 if l.tick == nil { 66 return 67 } 68 ch := l.tick.C 69 l.mu.Unlock() 70 <-ch 71 l.mu.Lock() 72 if l.tick == nil { 73 return 74 } 75 l.lock.Extend() 76 l.mu.Unlock() 77 } 78 }() 79 return nil 80 } 81 82 func (l *longOperation) Unlock() { 83 l.mu.Lock() 84 defer l.mu.Unlock() 85 if l.tick != nil { 86 l.tick.Stop() 87 l.tick = nil 88 } 89 l.lock.Unlock() 90 }