-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
338 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package pyramid | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"sync" | ||
) | ||
|
||
// directory synchronizes between file operations that might change (create/delete) directories | ||
type directory struct { | ||
// ceilingDir is the root directory of the FS - shouldn't never be deleted | ||
ceilingDir string | ||
mu sync.Mutex | ||
} | ||
|
||
// deleteDirRecIfEmpty deletes the given directory if it is empty. | ||
// It will continue to delete all parents directory if they are empty, until the ceilingDir. | ||
// Passed dir path isn't checked for malicious referencing (e.g. "../../../usr") and should never be | ||
// controlled by any user input. | ||
func (d *directory) deleteDirRecIfEmpty(dir string) error { | ||
d.mu.Lock() | ||
defer d.mu.Unlock() | ||
|
||
for dir != d.ceilingDir { | ||
empty, err := isDirEmpty(dir) | ||
if err != nil { | ||
if errors.Is(err, os.ErrNotExist) { | ||
return nil | ||
} | ||
return err | ||
} | ||
if !empty { | ||
return nil | ||
} | ||
|
||
parentDir := path.Dir(dir) | ||
if err := os.Remove(dir); err != nil { | ||
return err | ||
} | ||
dir = parentDir | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func isDirEmpty(name string) (bool, error) { | ||
f, err := os.Open(name) | ||
if err != nil { | ||
return false, err | ||
} | ||
defer f.Close() | ||
|
||
_, err = f.Readdirnames(1) | ||
if errors.Is(err, io.EOF) { | ||
return true, nil | ||
} | ||
return false, err | ||
} | ||
|
||
// createFile creates the file under the path and creates all parent dirs if missing. | ||
func (d *directory) createFile(path string) (*os.File, error) { | ||
d.mu.Lock() | ||
defer d.mu.Unlock() | ||
|
||
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { | ||
return nil, fmt.Errorf("creating dir: %w", err) | ||
} | ||
|
||
return os.Create(path) | ||
} | ||
|
||
// renameFile will move the src file to dst location and creates all parent dirs if missing. | ||
func (d *directory) renameFile(src, dst string) error { | ||
d.mu.Lock() | ||
defer d.mu.Unlock() | ||
|
||
if err := os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil { | ||
return fmt.Errorf("creating dir: %w", err) | ||
} | ||
|
||
return os.Rename(src, dst) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package pyramid | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"path" | ||
"strconv" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestConcurrentCreateDeleteDir(t *testing.T) { | ||
name, err := ioutil.TempDir("", "test-dir-") | ||
require.NoError(t, err) | ||
sut := directory{ceilingDir: name} | ||
|
||
var wg sync.WaitGroup | ||
concurrency := 1000 | ||
pathDir := path.Join(name, "a/b/c/") | ||
|
||
for i := 0; i < concurrency; i++ { | ||
// create and delete | ||
filepath := path.Join(pathDir, strconv.Itoa(i)) | ||
wg.Add(2) | ||
go func() { | ||
f, err := sut.createFile(filepath) | ||
require.NoError(t, err) | ||
require.NoError(t, f.Close()) | ||
require.NoError(t, os.Remove(filepath)) | ||
|
||
wg.Done() | ||
}() | ||
// delete folder - this will sometime succeed if the folder is empty | ||
go func() { | ||
require.NoError(t, sut.deleteDirRecIfEmpty(pathDir)) | ||
|
||
wg.Done() | ||
}() | ||
} | ||
// It doesn't really matter if the dir exists and its content. | ||
// It's more about not panicking thru all of this | ||
wg.Wait() | ||
} | ||
|
||
func TestConcurrentRenameDeleteDir(t *testing.T) { | ||
name, err := ioutil.TempDir("", "test-dir-") | ||
require.NoError(t, err) | ||
sut := directory{ceilingDir: name} | ||
|
||
var wg sync.WaitGroup | ||
concurrency := 1000 | ||
pathDir := path.Join(name, "a/b/c/") | ||
|
||
for i := 0; i < concurrency; i++ { | ||
// create and delete | ||
originalPath := path.Join(name, strconv.Itoa(i)) | ||
require.NoError(t, ioutil.WriteFile(originalPath, []byte("some data"), os.ModePerm)) | ||
|
||
filepath := path.Join(pathDir, strconv.Itoa(i)) | ||
wg.Add(2) | ||
go func() { | ||
err := sut.renameFile(originalPath, filepath) | ||
require.NoError(t, err) | ||
require.NoError(t, os.Remove(filepath)) | ||
|
||
wg.Done() | ||
}() | ||
// delete folder - this will sometime succeed if the folder is empty | ||
go func() { | ||
require.NoError(t, sut.deleteDirRecIfEmpty(pathDir)) | ||
|
||
wg.Done() | ||
}() | ||
} | ||
// It doesn't really matter if the dir exists and its content. | ||
// It's more about not panicking thru all of this | ||
wg.Wait() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package pyramid | ||
|
||
import ( | ||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/prometheus/client_golang/prometheus/promauto" | ||
) | ||
|
||
// nolint: gomnd | ||
const ( | ||
kb = float64(1024) | ||
fsNameLabel = "fsName" | ||
errorTypeLabel = "type" | ||
accessStatusLabel = "status" | ||
) | ||
|
||
var cacheAccess = promauto.NewCounterVec( | ||
prometheus.CounterOpts{ | ||
Name: "tier_fs_cache_hits_total", | ||
Help: "TierFS cache hits total count", | ||
}, []string{fsNameLabel, accessStatusLabel}) | ||
|
||
var errorsTotal = promauto.NewCounterVec( | ||
prometheus.CounterOpts{ | ||
Name: "tier_fs_errors_total", | ||
Help: "TierFS errors by type", | ||
}, []string{fsNameLabel, errorTypeLabel}) | ||
|
||
var evictionHistograms = promauto.NewHistogramVec( | ||
prometheus.HistogramOpts{ | ||
Name: "tier_fs_eviction_bytes", | ||
Help: "TierFS evicted object size by bytes", | ||
Buckets: prometheus.ExponentialBuckets(kb, 4, 7), | ||
}, | ||
[]string{fsNameLabel}) | ||
|
||
var downloadHistograms = promauto.NewHistogramVec( | ||
prometheus.HistogramOpts{ | ||
Name: "tier_fs_download_bytes", | ||
Help: "TierFS download from block-store object size by bytes", | ||
Buckets: prometheus.ExponentialBuckets(kb, 4, 7), | ||
}, | ||
[]string{fsNameLabel}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.