mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
Commit e323539115 introduced a check to
detect directories partially removed via IsPartiallyRemovedDir.
However, the check was performed using the full path, while de.Name()
returns only the current entry name (without the path). As a result, the
check always succeeded and the function did not behave as intended.
144 lines
4.8 KiB
Go
144 lines
4.8 KiB
Go
package fs
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
|
)
|
|
|
|
// directories with this filename are scheduled to be removed by MustRemoveDir().
|
|
const deleteDirFilename = ".delete-this-dir"
|
|
|
|
// MustRemoveDir removes the dirPath with all its contents.
|
|
//
|
|
// The dirPath contents may be partially deleted if unclean shutdown happens during the removal.
|
|
// The caller must verify whether the given directory is partially removed via IsPartiallyRemovedDir() call
|
|
// on the startup before using it. If the directory is partially removed, it must be removed again
|
|
// via MustRemoveDir() call.
|
|
func MustRemoveDir(dirPath string) {
|
|
if !IsPathExist(dirPath) {
|
|
// Nothing do delete.
|
|
return
|
|
}
|
|
|
|
// The code below is written in the way that partially deleted directories could be deleted
|
|
// on the next start after unclean shutdown, by verifying them with IsPartiallyRemovedDir() call.
|
|
//
|
|
// The code below doesn't depend on atomic renaming of directories, since it isn't supported
|
|
// by NFS and object storage.
|
|
|
|
// Create a deleteDirFilename file, which indicates that the dirPath must be removed.
|
|
deleteFilePath := filepath.Join(dirPath, deleteDirFilename)
|
|
f, err := os.Create(deleteFilePath)
|
|
if err != nil {
|
|
logger.Panicf("FATAL: cannot create %q while deleting %q: %s", deleteFilePath, dirPath, err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
logger.Panicf("FATAL: cannot close %q: %s", deleteFilePath, err)
|
|
}
|
|
|
|
// Make sure the deleteDirFilename file is visible in the dirPath.
|
|
MustSyncPath(dirPath)
|
|
|
|
// Remove the contents of the dirPath except of the deleteDirFilename file.
|
|
//
|
|
// Make this in parallel in order to reduce the time needed for the removal of big number of items
|
|
// on high-latency storage systems such as NFS.
|
|
// Directories for VitoriaLogs parts may contain big number of items when wide events are stored there.
|
|
// Also the number of parts in a partition may be quite big.
|
|
des := MustReadDir(dirPath)
|
|
var wg sync.WaitGroup
|
|
concurrencyCh := make(chan struct{}, min(32, len(des)-1))
|
|
for _, de := range des {
|
|
name := de.Name()
|
|
if name == deleteDirFilename {
|
|
continue
|
|
}
|
|
dirEntryPath := filepath.Join(dirPath, name)
|
|
|
|
concurrencyCh <- struct{}{}
|
|
wg.Go(func() {
|
|
if err := os.RemoveAll(dirEntryPath); err != nil {
|
|
logger.Panicf("FATAL: cannot remove %q: %s", dirEntryPath, err)
|
|
}
|
|
<-concurrencyCh
|
|
})
|
|
}
|
|
wg.Wait()
|
|
|
|
// Make sure the deleted names are properly synced to the dirPath,
|
|
// so they are no longer visible after unclean shutdown.
|
|
MustSyncPath(dirPath)
|
|
|
|
// Remove the deleteDirFilename file, since there are no other entries left in the directory.
|
|
MustRemovePath(deleteFilePath)
|
|
|
|
// Sync the directory after the removing deletDirFilename file in order to make sure
|
|
// all the metadata files are removed at some exotic filesystems such as OSSFS2.
|
|
// See https://github.com/VictoriaMetrics/VictoriaLogs/issues/649
|
|
// and https://github.com/VictoriaMetrics/VictoriaMetrics/pull/9709
|
|
MustSyncPath(dirPath)
|
|
|
|
// Remove the dirPath itself
|
|
MustRemovePath(dirPath)
|
|
|
|
// Do not sync the parent directory for the dirPath - the caller can do this if needed.
|
|
// It is OK if the dirPath will remain undeleted after unclean shutdown - it will be deleted
|
|
// on the next startup.
|
|
}
|
|
|
|
// IsPartiallyRemovedDir returns true if dirPath is partially removed because of unclean shutdown during the MustRemoveDir() call.
|
|
//
|
|
// The caller must call MustRemoveDir(dirPath) on partially removed dirPath.
|
|
func IsPartiallyRemovedDir(dirPath string) bool {
|
|
des := MustReadDir(dirPath)
|
|
if len(des) == 0 {
|
|
// Delete empty dirs too, since they may appear when the unclean shutdown happens after the deleteDirFilename is deleted,
|
|
// but before the directory is deleted itself.
|
|
return true
|
|
}
|
|
|
|
for _, de := range des {
|
|
if de.IsDir() {
|
|
continue
|
|
}
|
|
name := de.Name()
|
|
if name == deleteDirFilename {
|
|
// The directory contains the deleteDirFilename. This means it is partially deleted.
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// MustRemovePath removes the given path. It must be either a file or an empty directory.
|
|
//
|
|
// Use MustRemoveDir for removing non-empty directories.
|
|
func MustRemovePath(path string) {
|
|
if err := os.Remove(path); err != nil {
|
|
logger.Panicf("FATAL: cannot remove %q: %s", path, err)
|
|
}
|
|
}
|
|
|
|
// MustRemoveDirContents removes all the contents of the given dir if it exists.
|
|
//
|
|
// It doesn't remove the dir itself, so the dir may be mounted to a separate partition.
|
|
func MustRemoveDirContents(dir string) {
|
|
if !IsPathExist(dir) {
|
|
// The path doesn't exist, so nothing to remove.
|
|
return
|
|
}
|
|
|
|
des := MustReadDir(dir)
|
|
for _, de := range des {
|
|
name := de.Name()
|
|
fullPath := filepath.Join(dir, name)
|
|
if err := os.RemoveAll(fullPath); err != nil {
|
|
logger.Panicf("FATAL: cannot remove %s: %s", fullPath, err)
|
|
}
|
|
}
|
|
MustSyncPath(dir)
|
|
}
|