filelocker: Add tests

This commit is contained in:
Marius 2019-09-10 16:19:49 +02:00
parent 241c458184
commit 8e1dce1dcb
3 changed files with 66 additions and 16 deletions

View File

@ -1,19 +1,12 @@
// Package filestore provide a storage backend based on the local file system. // Package filelocker provide an upload locker based on the local file system.
// //
// FileStore is a storage backend used as a handler.DataStore in handler.NewHandler. // It provides an exclusive upload locking mechanism using lock files
// It stores the uploads in a directory specified in two different files: The
// `[id].info` files are used to store the fileinfo in JSON format. The
// `[id]` files without an extension contain the raw binary data uploaded.
// No cleanup is performed so you may want to run a cronjob to ensure your disk
// is not filled up with old and finished uploads.
//
// In addition, it provides an exclusive upload locking mechanism using lock files
// which are stored on disk. Each of them stores the PID of the process which // which are stored on disk. Each of them stores the PID of the process which
// acquired the lock. This allows locks to be automatically freed when a process // acquired the lock. This allows locks to be automatically freed when a process
// is unable to release it on its own because the process is not alive anymore. // is unable to release it on its own because the process is not alive anymore.
// For more information, consult the documentation for handler.LockerDataStore // For more information, consult the documentation for handler.LockerDataStore
// interface, which is implemented by FileStore // interface, which is implemented by FileLocker.
package filestore package filelocker
import ( import (
"os" "os"
@ -42,16 +35,16 @@ func New(path string) FileLocker {
return FileLocker{path} return FileLocker{path}
} }
func (locker FileLocker) NewLock(id string) (handler.UploadLock, error) { func (locker FileLocker) NewLock(id string) (handler.Lock, error) {
path, err := filepath.Abs(filepath.Join(locker.Path, id+".lock")) path, err := filepath.Abs(filepath.Join(locker.Path, id+".lock"))
if err != nil { if err != nil {
return lockfile.Lockfile(""), err return nil, err
} }
// We use Lockfile directly instead of lockfile.New to bypass the unnecessary // We use Lockfile directly instead of lockfile.New to bypass the unnecessary
// check whether the provided path is absolute since we just resolved it // check whether the provided path is absolute since we just resolved it
// on our own. // on our own.
return fileUploadLock{ return &fileUploadLock{
file: lockfile.Lockfile(path), file: lockfile.Lockfile(path),
}, nil }, nil
} }
@ -61,7 +54,7 @@ type fileUploadLock struct {
} }
func (lock fileUploadLock) Lock() error { func (lock fileUploadLock) Lock() error {
err = lock.file.TryLock() err := lock.file.TryLock()
if err == lockfile.ErrBusy { if err == lockfile.ErrBusy {
return handler.ErrFileLocked return handler.ErrFileLocked
} }
@ -70,7 +63,7 @@ func (lock fileUploadLock) Lock() error {
} }
func (lock fileUploadLock) Unlock() error { func (lock fileUploadLock) Unlock() error {
err = lock.file.Unlock() err := lock.file.Unlock()
// A "no such file or directory" will be returned if no lockfile was found. // A "no such file or directory" will be returned if no lockfile was found.
// Since this means that the file has never been locked, we drop the error // Since this means that the file has never been locked, we drop the error

View File

@ -0,0 +1,32 @@
package filelocker
import (
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tus/tusd/pkg/handler"
)
var _ handler.Locker = &FileLocker{}
func TestFileLocker(t *testing.T) {
a := assert.New(t)
dir, err := ioutil.TempDir("", "tusd-file-locker")
a.NoError(err)
locker := FileLocker{dir}
lock1, err := locker.NewLock("one")
a.NoError(err)
a.NoError(lock1.Lock())
a.Equal(handler.ErrFileLocked, lock1.Lock())
lock2, err := locker.NewLock("one")
a.NoError(err)
a.Equal(handler.ErrFileLocked, lock2.Lock())
a.NoError(lock1.Unlock())
}

View File

@ -144,3 +144,28 @@ type LengthDeferrerDataStore interface {
type LengthDeclarableUpload interface { type LengthDeclarableUpload interface {
DeclareLength(length int64) error DeclareLength(length int64) error
} }
// Locker is the interface required for custom lock persisting mechanisms.
// Common ways to store this information is in memory, on disk or using an
// external service, such as ZooKeeper.
// When multiple processes are attempting to access an upload, whether it be
// by reading or writing, a synchronization mechanism is required to prevent
// data corruption, especially to ensure correct offset values and the proper
// order of chunks inside a single upload.
type Locker interface {
// NewLock creates a new unlocked lock object for the given upload ID.
NewLock(id string) (Lock, error)
}
// Lock is the interface for a lock as returned from a Locker.
type Lock interface {
// Lock attempts to obtain an exclusive lock for the upload specified
// by its id.
// If this operation fails because the resource is already locked, the
// tusd.ErrFileLocked must be returned. If no error is returned, the attempt
// is consider to be successful and the upload to be locked until UnlockUpload
// is invoked for the same upload.
Lock() error
// Unlock releases an existing lock for the given upload.
Unlock() error
}