Under any concurrent removeObjects in progress might have removed the parents of the same prefix for which there is an ongoing putObject request. An inconsistent situation may arise as explained below even under sufficient locking. PutObject is almost successful at the last stage when a temporary file is renamed to its actual namespace at `a/b/c/object1`. Concurrently a RemoveObject is also in progress at the same prefix for an `a/b/c/object2`. To create the object1 at location `a/b/c` PutObject has to create all the parents recursively. ``` a/b/c - os.MkdirAll loops through has now created 'a/' and 'b/' about to create 'c/' a/b/c/object2 - at this point 'c/' and 'object2' are deleted about to delete b/ ``` Now for os.MkdirAll loop the expected situation is that top level parent 'a/b/' exists which it created , such that it can create 'c/' - since removeObject and putObject do not compete for lock due to holding locks at different resources. removeObject proceeds to delete parent 'b/' since 'c/' is not yet present, once deleted 'os.MkdirAll' would receive an error as syscall.ENOENT which would fail the putObject request. This PR tries to address this issue by implementing a safer/guarded approach where we would retry an operation such as `os.MkdirAll` and `os.Rename` if both operations observe syscall.ENOENT. Fixes #5254
78 lines
3 KiB
Go
78 lines
3 KiB
Go
/*
|
|
* Minio Cloud Storage, (C) 2018 Minio, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/minio/minio/pkg/errors"
|
|
)
|
|
|
|
// Tests - mkdirAll()
|
|
func TestOSMkdirAll(t *testing.T) {
|
|
// create posix test setup
|
|
_, path, err := newPosixTestSetup()
|
|
if err != nil {
|
|
t.Fatalf("Unable to create posix test setup, %s", err)
|
|
}
|
|
defer os.RemoveAll(path)
|
|
|
|
if err = mkdirAll("", 0777); errors.Cause(err) != errInvalidArgument {
|
|
t.Fatal("Unexpected error", err)
|
|
}
|
|
|
|
if err = mkdirAll(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), 0777); errors.Cause(err) != errFileNameTooLong {
|
|
t.Fatal("Unexpected error", err)
|
|
}
|
|
|
|
if err = mkdirAll(pathJoin(path, "success-vol", "success-object"), 0777); err != nil {
|
|
t.Fatal("Unexpected error", err)
|
|
}
|
|
}
|
|
|
|
// Tests - renameAll()
|
|
func TestOSRenameAll(t *testing.T) {
|
|
// create posix test setup
|
|
_, path, err := newPosixTestSetup()
|
|
if err != nil {
|
|
t.Fatalf("Unable to create posix test setup, %s", err)
|
|
}
|
|
defer os.RemoveAll(path)
|
|
|
|
if err = mkdirAll(pathJoin(path, "testvolume1"), 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = renameAll("", "foo"); err != errInvalidArgument {
|
|
t.Fatal(err)
|
|
}
|
|
if err = renameAll("foo", ""); err != errInvalidArgument {
|
|
t.Fatal(err)
|
|
}
|
|
if err = renameAll(pathJoin(path, "testvolume1"), pathJoin(path, "testvolume2")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = renameAll(pathJoin(path, "testvolume1"), pathJoin(path, "testvolume2")); errors.Cause(err) != errFileNotFound {
|
|
t.Fatal(err)
|
|
}
|
|
if err = renameAll(pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), pathJoin(path, "testvolume2")); errors.Cause(err) != errFileNameTooLong {
|
|
t.Fatal("Unexpected error", err)
|
|
}
|
|
if err = renameAll(pathJoin(path, "testvolume1"), pathJoin(path, "my-obj-del-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001")); errors.Cause(err) != errFileNameTooLong {
|
|
t.Fatal("Unexpected error", err)
|
|
}
|
|
}
|