forked from mirror/afero
Fix concurrency issue in MemMapFs.Mkdir/MkdirAll
* The backing map is protected by a RWMutex * This commit double checks for the existence of the directory inside the write lock to avoid potential data races when multiple goroutines tries to create the same directory. Fixes #361 Fixes #298
This commit is contained in:
parent
2a70f2bb2d
commit
a800a9de53
|
@ -142,6 +142,11 @@ func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
|
// Dobule check that it doesn't exist.
|
||||||
|
if _, ok := m.getData()[name]; ok {
|
||||||
|
m.mu.Unlock()
|
||||||
|
return &os.PathError{Op: "mkdir", Path: name, Err: ErrFileExists}
|
||||||
|
}
|
||||||
item := mem.CreateDir(name)
|
item := mem.CreateDir(name)
|
||||||
mem.SetMode(item, os.ModeDir|perm)
|
mem.SetMode(item, os.ModeDir|perm)
|
||||||
m.getData()[name] = item
|
m.getData()[name] = item
|
||||||
|
|
|
@ -3,9 +3,12 @@ package afero
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -692,3 +695,84 @@ func TestMemFsLstatIfPossible(t *testing.T) {
|
||||||
t.Fatalf("Function indicated lstat was called. This should never be true.")
|
t.Fatalf("Function indicated lstat was called. This should never be true.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMemMapFsConfurrentMkdir(t *testing.T) {
|
||||||
|
const dir = "test_dir"
|
||||||
|
const n = 1000
|
||||||
|
mfs := NewMemMapFs().(*MemMapFs)
|
||||||
|
|
||||||
|
allFilePaths := make([]string, 0, n)
|
||||||
|
|
||||||
|
// run concurrency test
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
fp := filepath.Join(
|
||||||
|
dir,
|
||||||
|
fmt.Sprintf("%02d", n%10),
|
||||||
|
fmt.Sprintf("%d.txt", i),
|
||||||
|
)
|
||||||
|
allFilePaths = append(allFilePaths, fp)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
if err := mfs.MkdirAll(filepath.Dir(fp), 0755); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wt, err := mfs.Create(fp)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := wt.Close(); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// write 30 bytes
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
_, err := wt.Write([]byte("000"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Test1: find all files by full path access
|
||||||
|
for _, fp := range allFilePaths {
|
||||||
|
info, err := mfs.Stat(fp)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Size() != 30 {
|
||||||
|
t.Errorf("file size should be 30, but got %d", info.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test2: find all files by walk
|
||||||
|
foundFiles := make([]string, 0, n)
|
||||||
|
wErr := Walk(mfs, dir, func(path string, info fs.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil // skip dir
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(info.Name(), ".txt") {
|
||||||
|
foundFiles = append(foundFiles, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if wErr != nil {
|
||||||
|
t.Error(wErr)
|
||||||
|
}
|
||||||
|
if len(foundFiles) != n {
|
||||||
|
t.Errorf("found %d files, but expect %d", len(foundFiles), n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue