2024-01-19 20:50:09 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"git.lumeweb.com/LumeWeb/portal/db/models"
|
|
|
|
"git.lumeweb.com/LumeWeb/portal/interfaces"
|
|
|
|
tusd "github.com/tus/tusd/v2/pkg/handler"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
_ tusd.Locker = (*MySQLLocker)(nil)
|
|
|
|
_ tusd.Lock = (*Lock)(nil)
|
|
|
|
)
|
|
|
|
|
|
|
|
type MySQLLocker struct {
|
|
|
|
storage interfaces.StorageService
|
|
|
|
AcquirerPollInterval time.Duration
|
|
|
|
HolderPollInterval time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
type Lock struct {
|
|
|
|
locker *MySQLLocker
|
|
|
|
id string
|
|
|
|
holderPollInterval time.Duration
|
|
|
|
acquirerPollInterval time.Duration
|
|
|
|
stopHolderPoll chan struct{}
|
|
|
|
lockRecord models.TusLock
|
|
|
|
once sync.Once
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewMySQLLocker(storage interfaces.StorageService) *MySQLLocker {
|
|
|
|
return &MySQLLocker{storage: storage, HolderPollInterval: 5 * time.Second, AcquirerPollInterval: 2 * time.Second}
|
|
|
|
}
|
|
|
|
|
2024-01-20 16:04:43 +00:00
|
|
|
func (l *Lock) released() error {
|
|
|
|
err := l.lockRecord.Released(l.locker.storage.Portal().Database())
|
|
|
|
if err != nil {
|
|
|
|
l.locker.storage.Portal().Logger().Error("Failed to release lock", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2024-01-19 20:50:09 +00:00
|
|
|
func (l *Lock) Lock(ctx context.Context, requestUnlock func()) error {
|
|
|
|
|
|
|
|
db := l.locker.storage.Portal().Database()
|
|
|
|
|
|
|
|
for {
|
|
|
|
err := l.lockRecord.TryLock(db, ctx)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != models.ErrTusLockBusy {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-01-20 15:48:42 +00:00
|
|
|
err = l.lockRecord.RequestRelease(db)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-01-19 20:50:09 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2024-01-20 16:06:50 +00:00
|
|
|
err := l.released()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-19 20:50:09 +00:00
|
|
|
// Context expired, so we return a timeout
|
|
|
|
return tusd.ErrLockTimeout
|
|
|
|
case <-time.After(l.acquirerPollInterval):
|
|
|
|
// Continue with the next attempt after a short delay
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
2024-01-20 16:16:54 +00:00
|
|
|
ticker := time.NewTicker(l.holderPollInterval)
|
|
|
|
defer ticker.Stop()
|
|
|
|
|
2024-01-19 20:50:09 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-l.stopHolderPoll:
|
|
|
|
return
|
2024-01-20 16:16:54 +00:00
|
|
|
case <-ticker.C:
|
2024-01-19 20:50:09 +00:00
|
|
|
requested, err := l.lockRecord.IsReleaseRequested(db)
|
2024-01-20 16:16:54 +00:00
|
|
|
if err != nil {
|
|
|
|
// Handle error
|
2024-01-19 20:50:09 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if requested {
|
|
|
|
requestUnlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *Lock) Unlock() error {
|
|
|
|
l.once.Do(func() {
|
|
|
|
close(l.stopHolderPoll)
|
|
|
|
})
|
|
|
|
|
|
|
|
return l.lockRecord.Delete(l.locker.storage.Portal().Database())
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MySQLLocker) NewLock(id string) (tusd.Lock, error) {
|
|
|
|
return &Lock{
|
|
|
|
locker: m,
|
|
|
|
id: id,
|
|
|
|
holderPollInterval: m.HolderPollInterval,
|
|
|
|
acquirerPollInterval: m.AcquirerPollInterval,
|
|
|
|
stopHolderPoll: make(chan struct{}),
|
|
|
|
lockRecord: models.TusLock{
|
|
|
|
LockId: id,
|
|
|
|
HolderPID: os.Getpid(),
|
|
|
|
AcquiredAt: time.Now(),
|
|
|
|
ExpiresAt: time.Now().Add(30 * time.Minute),
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|