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  }