Skip to content

Commit

Permalink
posix: do not upstream errors in deleteFile (minio#4771)
Browse files Browse the repository at this point in the history
This commit changes posix's deleteFile() to not upstream errors from
removing parent directories. This fixes a race condition.

The race condition occurs when multiple deleteFile()s are called on the
same parent directory, but different child files. Because deleteFile()
recursively removes parent directories if they are empty, but
deleteFile() errors if the selected deletePath does not exist, there was
an opportunity for a race condition. The two processes would remove the
child directories successfully, then depend on the parent directory
still existing. In some cases this is an invalid assumption, because
other processes can remove the parent directory beforehand. This commit
changes deleteFile() to not upstream an error if one occurs, because the
only required error should be from the immediate deletePath, not from a
parent path.

In the specific bug report, multiple CompleteMultipartUpload requests
would launch multiple deleteFile() requests. Because they chain up on
parent directories, ultimately at the end, there would be multiple
remove files for the ultimate parent directory,
.minio.sys/multipart/{bucket}. Because only one will succeed and one
will fail, an error would be upstreamed saying that the file does not
exist, and the CompleteMultipartUpload code interpreted this as
NoSuchKey, or that the object/part id doesn't exist. This was faulty
behavior and is now fixed.

The added test fails before this change and passes after this change.

Fixes: minio#4727
  • Loading branch information
brendanashworth authored and deekoder committed Aug 4, 2017
1 parent 54f3a09 commit aeafe66
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 1 deletion.
5 changes: 4 additions & 1 deletion cmd/posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,10 @@ func deleteFile(basePath, deletePath string) error {
}

// Recursively go down the next path and delete again.
return deleteFile(basePath, slashpath.Dir(deletePath))
// Errors for parent directories shouldn't trickle down.
deleteFile(basePath, slashpath.Dir(deletePath))

return nil
}

// DeleteFile - delete a file at path.
Expand Down
20 changes: 20 additions & 0 deletions cmd/posix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,17 @@ func TestPosixDeleteFile(t *testing.T) {
t.Fatalf("Unable to create file, %s", err)
}

if err = posixStorage.MakeVol("no-permissions"); err != nil {
t.Fatalf("Unable to create volume, %s", err.Error())
}
if err = posixStorage.AppendFile("no-permissions", "dir/file", []byte("Hello, world")); err != nil {
t.Fatalf("Unable to create file, %s", err.Error())
}
// Parent directory must have write permissions, this is read + execute.
if err = os.Chmod(pathJoin(path, "no-permissions"), 0555); err != nil {
t.Fatalf("Unable to chmod directory, %s", err.Error())
}

testCases := []struct {
srcVol string
srcPath string
Expand Down Expand Up @@ -796,6 +807,15 @@ func TestPosixDeleteFile(t *testing.T) {
ioErrCnt: 0,
expectedErr: errFileNameTooLong,
},
// TestPosix case - 7.
// TestPosix case with undeletable parent directory.
// File can delete, dir cannot delete because no-permissions doesn't have write perms.
{
srcVol: "no-permissions",
srcPath: "dir/file",
ioErrCnt: 0,
expectedErr: nil,
},
}

for i, testCase := range testCases {
Expand Down

0 comments on commit aeafe66

Please sign in to comment.