Merge pull request #1633 from calmh/errorstate

Move folder errors to state
This commit is contained in:
Audrius Butkevicius 2015-04-13 00:48:13 +01:00
commit ba4a6fc0c5
11 changed files with 128 additions and 108 deletions

View File

@ -354,7 +354,12 @@ func folderSummary(m *model.Model, folder string) map[string]interface{} {
res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
res["state"], res["stateChanged"] = m.State(folder)
var err error
res["state"], res["stateChanged"], err = m.State(folder)
if err != nil {
res["error"] = err.Error()
}
res["version"] = m.CurrentLocalVersion(folder) + m.RemoteLocalVersion(folder)
ignorePatterns, _, _ := m.GetIgnores(folder)

View File

@ -42,6 +42,7 @@ func TestFolderErrors(t *testing.T) {
m := model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m.AddFolder(fcfg)
m.StartFolderRW("folder")
if err := m.CheckFolderHealth("folder"); err != nil {
t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
@ -69,6 +70,7 @@ func TestFolderErrors(t *testing.T) {
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m.AddFolder(fcfg)
m.StartFolderRW("folder")
if err := m.CheckFolderHealth("folder"); err != nil {
t.Error("Unexpected error", cfg.Folders()["folder"].Invalid)
@ -90,8 +92,9 @@ func TestFolderErrors(t *testing.T) {
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m.AddFolder(fcfg)
m.StartFolderRW("folder")
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder marker missing" {
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder marker missing" {
t.Error("Incorrect error: Folder marker missing !=", m.CheckFolderHealth("folder"))
}
@ -117,8 +120,9 @@ func TestFolderErrors(t *testing.T) {
m = model.NewModel(cfg, protocol.LocalDeviceID, "device", "syncthing", "dev", ldb)
m.AddFolder(fcfg)
m.StartFolderRW("folder")
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder path missing" {
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder path missing" {
t.Error("Incorrect error: Folder path missing !=", m.CheckFolderHealth("folder"))
}
@ -126,7 +130,7 @@ func TestFolderErrors(t *testing.T) {
os.Mkdir("testdata/testfolder", 0700)
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "Folder marker missing" {
if err := m.CheckFolderHealth("folder"); err == nil || err.Error() != "folder marker missing" {
t.Error("Incorrect error: Folder marker missing !=", m.CheckFolderHealth("folder"))
}

View File

@ -193,9 +193,9 @@
<th><span class="glyphicon glyphicon-folder-open"></span>&emsp;<span translate>Folder Path</span></th>
<td class="text-right">{{folder.path}}</td>
</tr>
<tr ng-if="model[folder.id].invalid">
<tr ng-if="model[folder.id].invalid || model[folder.id].error">
<th><span class="glyphicon glyphicon-warning-sign"></span>&emsp;<span translate>Error</span></th>
<td class="text-right">{{model[folder.id].invalid}}</td>
<td class="text-right">{{model[folder.id].invalid || model[folder.id].error}}</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-globe"></span>&emsp;<span translate>Global State</span></th>

View File

@ -461,10 +461,14 @@ angular.module('syncthing.core')
return 'unshared';
}
if ($scope.model[folderCfg.id].invalid !== '') {
if ($scope.model[folderCfg.id].invalid) {
return 'stopped';
}
if ($scope.model[folderCfg.id].state == 'error') {
return 'stopped'; // legacy, the state is called "stopped" in the GUI
}
return '' + $scope.model[folderCfg.id].state;
};
@ -494,6 +498,9 @@ angular.module('syncthing.core')
if (state == 'scanning') {
return 'primary';
}
if (state == 'error') {
return 'danger';
}
return 'info';
};

File diff suppressed because one or more lines are too long

View File

@ -215,29 +215,6 @@ func (w *Wrapper) SetGUI(gui GUIConfiguration) {
w.replaces <- w.cfg.Copy()
}
// Sets the folder error state. Emits ConfigSaved to cause a GUI refresh.
func (w *Wrapper) SetFolderError(id string, err error) {
w.mut.Lock()
defer w.mut.Unlock()
w.folderMap = nil
for i := range w.cfg.Folders {
if w.cfg.Folders[i].ID == id {
errstr := ""
if err != nil {
errstr = err.Error()
}
if errstr != w.cfg.Folders[i].Invalid {
w.cfg.Folders[i].Invalid = errstr
events.Default.Log(events.ConfigSaved, w.cfg)
w.replaces <- w.cfg.Copy()
}
return
}
}
}
// Returns whether or not connection attempts from the given device should be
// silently ignored.
func (w *Wrapper) IgnoredDevice(id protocol.DeviceID) bool {

View File

@ -1,17 +1,8 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package model
@ -28,7 +19,7 @@ const (
FolderIdle folderState = iota
FolderScanning
FolderSyncing
FolderCleaning
FolderError
)
func (s folderState) String() string {
@ -37,10 +28,10 @@ func (s folderState) String() string {
return "idle"
case FolderScanning:
return "scanning"
case FolderCleaning:
return "cleaning"
case FolderSyncing:
return "syncing"
case FolderError:
return "error"
default:
return "unknown"
}
@ -51,10 +42,16 @@ type stateTracker struct {
mut sync.Mutex
current folderState
err error
changed time.Time
}
// setState sets the new folder state, for states other than FolderError.
func (s *stateTracker) setState(newState folderState) {
if newState == FolderError {
panic("must use setError")
}
s.mut.Lock()
if newState != s.current {
/* This should hold later...
@ -74,6 +71,7 @@ func (s *stateTracker) setState(newState folderState) {
}
s.current = newState
s.err = nil
s.changed = time.Now()
events.Default.Log(events.StateChanged, eventData)
@ -81,9 +79,35 @@ func (s *stateTracker) setState(newState folderState) {
s.mut.Unlock()
}
func (s *stateTracker) getState() (current folderState, changed time.Time) {
// getState returns the current state, the time when it last changed, and the
// current error or nil.
func (s *stateTracker) getState() (current folderState, changed time.Time, err error) {
s.mut.Lock()
current, changed = s.current, s.changed
current, changed, err = s.current, s.changed, s.err
s.mut.Unlock()
return
}
// setError sets the folder state to FolderError with the specified error.
func (s *stateTracker) setError(err error) {
s.mut.Lock()
if s.current != FolderError || s.err.Error() != err.Error() {
eventData := map[string]interface{}{
"folder": s.folder,
"to": FolderError.String(),
"from": s.current.String(),
"error": err.Error(),
}
if !s.changed.IsZero() {
eventData["duration"] = time.Since(s.changed).Seconds()
}
s.current = FolderError
s.err = err
s.changed = time.Now()
events.Default.Log(events.StateChanged, eventData)
}
s.mut.Unlock()
}

View File

@ -48,8 +48,9 @@ type service interface {
Jobs() ([]string, []string) // In progress, Queued
BringToFront(string)
setState(folderState)
getState() (folderState, time.Time)
setState(state folderState)
setError(err error)
getState() (folderState, time.Time, error)
}
type Model struct {
@ -1083,13 +1084,13 @@ func (m *Model) AddFolder(cfg config.FolderConfiguration) {
func (m *Model) ScanFolders() map[string]error {
m.fmut.RLock()
var folders = make([]string, 0, len(m.folderCfgs))
folders := make([]string, 0, len(m.folderCfgs))
for folder := range m.folderCfgs {
folders = append(folders, folder)
}
m.fmut.RUnlock()
var errors = make(map[string]error, len(m.folderCfgs))
errors := make(map[string]error, len(m.folderCfgs))
var errorsMut sync.Mutex
var wg sync.WaitGroup
@ -1102,11 +1103,15 @@ func (m *Model) ScanFolders() map[string]error {
errorsMut.Lock()
errors[folder] = err
errorsMut.Unlock()
// Potentially sets the error twice, once in the scanner just
// by doing a check, and once here, if the error returned is
// the same one as returned by CheckFolderHealth, though
// duplicate set is handled by SetFolderError
m.cfg.SetFolderError(folder, err)
// duplicate set is handled by setError.
m.fmut.RLock()
srv := m.folderRunners[folder]
m.fmut.RUnlock()
srv.setError(err)
}
wg.Done()
}()
@ -1182,13 +1187,13 @@ nextSub:
}
runner.setState(FolderScanning)
defer runner.setState(FolderIdle)
fchan, err := w.Walk()
fchan, err := w.Walk()
if err != nil {
m.cfg.SetFolderError(folder, err)
runner.setError(err)
return err
}
batchSize := 100
batch := make([]protocol.FileInfo, 0, batchSize)
for f := range fchan {
@ -1298,6 +1303,7 @@ nextSub:
fs.Update(protocol.LocalDeviceID, batch)
}
runner.setState(FolderIdle)
return nil
}
@ -1340,15 +1346,18 @@ func (m *Model) clusterConfig(device protocol.DeviceID) protocol.ClusterConfigMe
return cm
}
func (m *Model) State(folder string) (string, time.Time) {
func (m *Model) State(folder string) (string, time.Time, error) {
m.fmut.RLock()
runner, ok := m.folderRunners[folder]
m.fmut.RUnlock()
if !ok {
return "", time.Time{}
// The returned error should be an actual folder error, so returning
// errors.New("does not exist") or similar here would be
// inappropriate.
return "", time.Time{}, nil
}
state, changed := runner.getState()
return state.String(), changed
state, changed, err := runner.getState()
return state.String(), changed, err
}
func (m *Model) Override(folder string) {
@ -1528,7 +1537,7 @@ func (m *Model) BringToFront(folder, file string) {
func (m *Model) CheckFolderHealth(id string) error {
folder, ok := m.cfg.Folders()[id]
if !ok {
return errors.New("Folder does not exist")
return errors.New("folder does not exist")
}
fi, err := os.Stat(folder.Path())
@ -1538,9 +1547,9 @@ func (m *Model) CheckFolderHealth(id string) error {
// that all files have been deleted which might not be the case,
// so mark it as invalid instead.
if err != nil || !fi.IsDir() {
err = errors.New("Folder path missing")
err = errors.New("folder path missing")
} else if !folder.HasMarker() {
err = errors.New("Folder marker missing")
err = errors.New("folder marker missing")
}
} else if os.IsNotExist(err) {
// If we don't have any files in the index, and the directory
@ -1555,35 +1564,21 @@ func (m *Model) CheckFolderHealth(id string) error {
err = folder.CreateMarker()
}
if err == nil {
if folder.Invalid != "" {
l.Infof("Starting folder %q after error %q", folder.ID, folder.Invalid)
m.cfg.SetFolderError(id, nil)
m.fmut.RLock()
runner := m.folderRunners[folder.ID]
m.fmut.RUnlock()
_, _, oldErr := runner.getState()
if err != nil {
if oldErr != nil && oldErr.Error() != err.Error() {
l.Infof("Folder %q error changed: %q -> %q", folder.ID, oldErr, err)
} else if oldErr == nil {
l.Warnf("Stopping folder %q - %v", folder.ID, err)
}
if folder, ok := m.cfg.Folders()[id]; !ok || folder.Invalid != "" {
panic("Unable to unset folder \"" + id + "\" error.")
}
return nil
}
if folder.Invalid == err.Error() {
return err
}
// folder is a copy of the original struct, hence Invalid value is
// preserved after the set.
m.cfg.SetFolderError(id, err)
if folder.Invalid == "" {
l.Warnf("Stopping folder %q - %v", folder.ID, err)
} else {
l.Infof("Folder %q error changed: %q -> %q", folder.ID, folder.Invalid, err)
}
if folder, ok := m.cfg.Folders()[id]; !ok || folder.Invalid != err.Error() {
panic("Unable to set folder \"" + id + "\" error.")
runner.setError(err)
} else if oldErr != nil {
l.Infof("Folder %q error is cleared, restarting", folder.ID)
runner.setState(FolderIdle)
}
return err

View File

@ -621,21 +621,25 @@ func TestROScanRecovery(t *testing.T) {
if time.Now().After(timeout) {
return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
}
if m.cfg.Folders()["default"].Invalid == status {
_, _, err := m.State("default")
if err == nil && status == "" {
return nil
}
if err != nil && err.Error() == status {
return nil
}
time.Sleep(10 * time.Millisecond)
}
}
if err := waitFor("Folder path missing"); err != nil {
if err := waitFor("folder path missing"); err != nil {
t.Error(err)
return
}
os.Mkdir(fcfg.RawPath, 0700)
if err := waitFor("Folder marker missing"); err != nil {
if err := waitFor("folder marker missing"); err != nil {
t.Error(err)
return
}
@ -654,14 +658,14 @@ func TestROScanRecovery(t *testing.T) {
os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
if err := waitFor("Folder marker missing"); err != nil {
if err := waitFor("folder marker missing"); err != nil {
t.Error(err)
return
}
os.Remove(fcfg.RawPath)
if err := waitFor("Folder path missing"); err != nil {
if err := waitFor("folder path missing"); err != nil {
t.Error(err)
return
}
@ -701,21 +705,25 @@ func TestRWScanRecovery(t *testing.T) {
if time.Now().After(timeout) {
return fmt.Errorf("Timed out waiting for status: %s, current status: %s", status, m.cfg.Folders()["default"].Invalid)
}
if m.cfg.Folders()["default"].Invalid == status {
_, _, err := m.State("default")
if err == nil && status == "" {
return nil
}
if err != nil && err.Error() == status {
return nil
}
time.Sleep(10 * time.Millisecond)
}
}
if err := waitFor("Folder path missing"); err != nil {
if err := waitFor("folder path missing"); err != nil {
t.Error(err)
return
}
os.Mkdir(fcfg.RawPath, 0700)
if err := waitFor("Folder marker missing"); err != nil {
if err := waitFor("folder marker missing"); err != nil {
t.Error(err)
return
}
@ -734,14 +742,14 @@ func TestRWScanRecovery(t *testing.T) {
os.Remove(filepath.Join(fcfg.RawPath, ".stfolder"))
if err := waitFor("Folder marker missing"); err != nil {
if err := waitFor("folder marker missing"); err != nil {
t.Error(err)
return
}
os.Remove(fcfg.RawPath)
if err := waitFor("Folder path missing"); err != nil {
if err := waitFor("folder path missing"); err != nil {
t.Error(err)
return
}

View File

@ -67,8 +67,8 @@ func (s *roFolder) Serve() {
// Potentially sets the error twice, once in the scanner just
// by doing a check, and once here, if the error returned is
// the same one as returned by CheckFolderHealth, though
// duplicate set is handled by SetFolderError
s.model.cfg.SetFolderError(s.folder, err)
// duplicate set is handled by setError.
s.setError(err)
reschedule()
continue
}

View File

@ -245,8 +245,8 @@ func (p *rwFolder) Serve() {
// Potentially sets the error twice, once in the scanner just
// by doing a check, and once here, if the error returned is
// the same one as returned by CheckFolderHealth, though
// duplicate set is handled by SetFolderError
p.model.cfg.SetFolderError(p.folder, err)
// duplicate set is handled by setError.
p.setError(err)
rescheduleScan()
continue
}