diff --git a/pkg/filelocker/filelocker.go b/pkg/filelocker/filelocker.go index 3692bade1..13e9005e8 100644 --- a/pkg/filelocker/filelocker.go +++ b/pkg/filelocker/filelocker.go @@ -18,6 +18,8 @@ package filelocker import ( "context" + "errors" + "io/fs" "os" "path/filepath" "time" @@ -104,6 +106,17 @@ func (lock fileUploadLock) Lock(ctx context.Context, requestRelease func()) erro continue } } + // If the upload ID uses a folder structure (e.g. projectA/upload1), the directory + // (e.g. projectA) might not exist, if no such upload exists already. In those cases, + // we just return ErrNotFound because no such upload exists anyways. + // TODO: This assumes that filelocker is used mostly with filestore, which is likely + // true, but does not have to be. If another storage backend is used, we cannot make + // any assumption about the folder structure. As an alternative, we should consider + // normalizing the upload ID to remove the folder structure as well an turn projectA/upload1 + // into projectA-upload1. + if errors.Is(err, fs.ErrNotExist) { + return handler.ErrNotFound + } if err != lockfile.ErrBusy { // If we get something different than ErrBusy, bubble the error up. return err diff --git a/pkg/filelocker/filelocker_test.go b/pkg/filelocker/filelocker_test.go index bd288a58f..f561d65a5 100644 --- a/pkg/filelocker/filelocker_test.go +++ b/pkg/filelocker/filelocker_test.go @@ -60,7 +60,7 @@ func TestFileLocker_Timeout(t *testing.T) { assertEmptyDirectory(dir, a) } -func TestMemoryLocker_RequestUnlock(t *testing.T) { +func TestFileLocker_RequestUnlock(t *testing.T) { a := assert.New(t) dir, err := os.MkdirTemp("", "tusd-file-locker") @@ -95,6 +95,26 @@ func TestMemoryLocker_RequestUnlock(t *testing.T) { assertEmptyDirectory(dir, a) } +func TestFileLocker_DirectoryNotFound(t *testing.T) { + a := assert.New(t) + + dir, err := os.MkdirTemp("", "tusd-file-locker") + a.NoError(err) + + locker := New(dir) + + // The upload ID uses a directory structure, but the corresponding directories do + // not exist. Since there can also exist no info file in this folder, we expect + // the locker to return ErrNotFound + lock, err := locker.NewLock("nested/folder/structure/upload") + a.Nil(err) + + err = lock.Lock(context.Background(), func() { + panic("must not be called") + }) + a.Equal(handler.ErrNotFound, err) +} + func assertEmptyDirectory(dir string, a *assert.Assertions) { file, err := os.Open(dir) a.NoError(err)