Stop folder when running out of disk space (fixes #2057)

& tweaks by calmh
This commit is contained in:
Lode Hoste
2015-07-16 12:52:36 +02:00
committed by Jakob Borg
parent 6a58033f2b
commit dfaa999291
17 changed files with 217 additions and 1 deletions

View File

@@ -26,7 +26,7 @@ import (
const (
OldestHandledVersion = 5
CurrentVersion = 10
CurrentVersion = 11
MaxRescanIntervalS = 365 * 24 * 60 * 60
)
@@ -74,6 +74,7 @@ type FolderConfiguration struct {
RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS"`
IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"`
AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize"`
MinDiskFreePct int `xml:"minDiskFreePct" json:"minDiskFreePct"`
Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
Copiers int `xml:"copiers" json:"copiers"` // This defines how many files are handled concurrently.
Pullers int `xml:"pullers" json:"pullers"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
@@ -364,6 +365,9 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
if cfg.Version == 9 {
convertV9V10(cfg)
}
if cfg.Version == 10 {
convertV10V11(cfg)
}
// Hash old cleartext passwords
if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' {
@@ -460,6 +464,14 @@ func ChangeRequiresRestart(from, to Configuration) bool {
return false
}
func convertV10V11(cfg *Configuration) {
// Set minimum disk free of existing folders to 1%
for i := range cfg.Folders {
cfg.Folders[i].MinDiskFreePct = 1
}
cfg.Version = 11
}
func convertV9V10(cfg *Configuration) {
// Enable auto normalization on existing folders.
for i := range cfg.Folders {

View File

@@ -92,6 +92,7 @@ func TestDeviceConfig(t *testing.T) {
Pullers: 16,
Hashers: 0,
AutoNormalize: true,
MinDiskFreePct: 1,
},
}
expectedDevices := []DeviceConfiguration{

View File

@@ -96,6 +96,10 @@ func Load(path string, myID protocol.DeviceID) (*Wrapper, error) {
return Wrap(path, cfg), nil
}
func (w *Wrapper) ConfigPath() string {
return w.path
}
// Stop stops the Serve() loop. Set and Replace operations will panic after a
// Stop.
func (w *Wrapper) Stop() {

View File

@@ -45,6 +45,7 @@ const (
indexBatchSize = 1000 // Either way, don't include more files than this
reqValidationTime = time.Hour // How long to cache validation entries for Request messages
reqValidationCacheSize = 1000 // How many entries to aim for in the validation cache size
minHomeDiskFreePct = 1.0 // Stop when less space than this is available on the home (config & db) disk
)
type service interface {
@@ -1230,6 +1231,10 @@ func (m *Model) internalScanFolderSubs(folder string, subs []string) error {
return errors.New("no such folder")
}
if err := m.CheckFolderHealth(folder); err != nil {
return err
}
_ = ignores.Load(filepath.Join(folderCfg.Path(), ".stignore")) // Ignore error, there might not be an .stignore
// Required to make sure that we start indexing at a directory we're already
@@ -1658,6 +1663,10 @@ func (m *Model) BringToFront(folder, file string) {
// CheckFolderHealth checks the folder for common errors and returns the
// current folder error, or nil if the folder is healthy.
func (m *Model) CheckFolderHealth(id string) error {
if free, err := osutil.DiskFreePercentage(m.cfg.ConfigPath()); err == nil && free < minHomeDiskFreePct {
return errors.New("out of disk space")
}
folder, ok := m.cfg.Folders()[id]
if !ok {
return errors.New("folder does not exist")
@@ -1673,6 +1682,8 @@ func (m *Model) CheckFolderHealth(id string) error {
err = errors.New("folder path missing")
} else if !folder.HasMarker() {
err = errors.New("folder marker missing")
} else if free, errDfp := osutil.DiskFreePercentage(folder.Path()); errDfp == nil && free < float64(folder.MinDiskFreePct) {
err = errors.New("out of disk space")
}
} else if os.IsNotExist(err) {
// If we don't have any files in the index, and the directory

View File

@@ -437,6 +437,7 @@ func (p *rwFolder) pullerIteration(ignores *ignore.Matcher) int {
// !!!
changed := 0
pullFileSize := int64(0)
fileDeletions := map[string]protocol.FileInfo{}
dirDeletions := []protocol.FileInfo{}
@@ -485,6 +486,7 @@ func (p *rwFolder) pullerIteration(ignores *ignore.Matcher) int {
default:
// A new or changed file or symlink. This is the only case where we
// do stuff concurrently in the background
pullFileSize += file.Size()
p.queue.Push(file.Name, file.Size(), file.Modified)
}
@@ -492,6 +494,17 @@ func (p *rwFolder) pullerIteration(ignores *ignore.Matcher) int {
return true
})
// Check if we are able to store all files on disk
if pullFileSize > 0 {
folder, ok := p.model.cfg.Folders()[p.folder]
if ok {
if free, err := osutil.DiskFreeBytes(folder.Path()); err == nil && free < pullFileSize {
l.Infof("Puller (folder %q): insufficient disk space available to pull %d files (%.2fMB)", p.folder, changed, float64(pullFileSize)/1024/1024)
return 0
}
}
}
// Reorder the file queue according to configuration
switch p.order {

View File

@@ -16,6 +16,7 @@ import (
"runtime"
"strings"
"github.com/calmh/du"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -210,3 +211,13 @@ func init() {
func IsWindowsExecutable(path string) bool {
return execExts[strings.ToLower(filepath.Ext(path))]
}
func DiskFreeBytes(path string) (free int64, err error) {
u, err := du.Get(path)
return u.FreeBytes, err
}
func DiskFreePercentage(path string) (freePct float64, err error) {
u, err := du.Get(path)
return (float64(u.FreeBytes) / float64(u.TotalBytes)) * 100, err
}

View File

@@ -164,3 +164,18 @@ func TestInWritableDirWindowsRename(t *testing.T) {
}
}
}
func TestDiskUsage(t *testing.T) {
free, err := osutil.DiskFreePercentage(".")
if err != nil {
if runtime.GOOS == "netbsd" ||
runtime.GOOS == "openbsd" ||
runtime.GOOS == "solaris" {
t.Skip()
}
t.Errorf("Unexpected error: %s", err)
}
if free < 1 {
t.Error("Disk is full?", free)
}
}