posix: do not upstream errors in deleteFile (#4771)

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: https://github.com/minio/minio/issues/4727
master
Brendan Ashworth 7 years ago committed by Dee Koder
parent 54f3a0946f
commit aeafe668d8
  1. 5
      cmd/posix.go
  2. 20
      cmd/posix_test.go

@ -912,7 +912,10 @@ func deleteFile(basePath, deletePath string) error {
} }
// Recursively go down the next path and delete again. // 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. // DeleteFile - delete a file at path.

@ -742,6 +742,17 @@ func TestPosixDeleteFile(t *testing.T) {
t.Fatalf("Unable to create file, %s", err) 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 { testCases := []struct {
srcVol string srcVol string
srcPath string srcPath string
@ -796,6 +807,15 @@ func TestPosixDeleteFile(t *testing.T) {
ioErrCnt: 0, ioErrCnt: 0,
expectedErr: errFileNameTooLong, 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 { for i, testCase := range testCases {

Loading…
Cancel
Save