mirror of
https://github.com/octoleo/syncthing.git
synced 2025-02-02 11:58:28 +00:00
* lib/fs, lib/model: Add error channel to Watch to avoid panics (fixes #5697) * forgot unsupported watch * and more non(-standard)-unixy fixes * and windows test * review
This commit is contained in:
parent
9e6db72535
commit
486230768e
@ -8,6 +8,7 @@ package fs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -334,3 +335,13 @@ func longFilenameSupport(path string) string {
|
|||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrWatchEventOutsideRoot struct{ msg string }
|
||||||
|
|
||||||
|
func (e *ErrWatchEventOutsideRoot) Error() string {
|
||||||
|
return e.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *BasicFilesystem) newErrWatchEventOutsideRoot(absPath, root string) *ErrWatchEventOutsideRoot {
|
||||||
|
return &ErrWatchEventOutsideRoot{fmt.Sprintf("Watching for changes encountered an event outside of the filesystem root: f.root==%v, root==%v, path==%v. This should never happen, please report this message to forum.syncthing.net.", f.root, root, absPath)}
|
||||||
|
}
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@ -58,17 +57,17 @@ func (f *BasicFilesystem) Roots() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unrootedChecked returns the path relative to the folder root (same as
|
// unrootedChecked returns the path relative to the folder root (same as
|
||||||
// unrooted). It panics if the given path is not a subpath and handles the
|
// unrooted) or an error if the given path is not a subpath and handles the
|
||||||
// special case when the given path is the folder root without a trailing
|
// special case when the given path is the folder root without a trailing
|
||||||
// pathseparator.
|
// pathseparator.
|
||||||
func (f *BasicFilesystem) unrootedChecked(absPath, root string) string {
|
func (f *BasicFilesystem) unrootedChecked(absPath, root string) (string, *ErrWatchEventOutsideRoot) {
|
||||||
if absPath+string(PathSeparator) == root {
|
if absPath+string(PathSeparator) == root {
|
||||||
return "."
|
return ".", nil
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(absPath, root) {
|
if !strings.HasPrefix(absPath, root) {
|
||||||
panic(fmt.Sprintf("bug: Notify backend is processing a change outside of the filesystem root: f.root==%v, root==%v, path==%v", f.root, root, absPath))
|
return "", f.newErrWatchEventOutsideRoot(absPath, root)
|
||||||
}
|
}
|
||||||
return rel(absPath, root)
|
return rel(absPath, root), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rel(path, prefix string) string {
|
func rel(path, prefix string) string {
|
||||||
|
@ -20,10 +20,10 @@ import (
|
|||||||
// Not meant to be changed, but must be changeable for tests
|
// Not meant to be changed, but must be changeable for tests
|
||||||
var backendBuffer = 500
|
var backendBuffer = 500
|
||||||
|
|
||||||
func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) {
|
func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||||
watchPath, root, err := f.watchPaths(name)
|
watchPath, root, err := f.watchPaths(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
outChan := make(chan Event)
|
outChan := make(chan Event)
|
||||||
@ -36,7 +36,11 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
|
|||||||
|
|
||||||
if ignore.SkipIgnoredDirs() {
|
if ignore.SkipIgnoredDirs() {
|
||||||
absShouldIgnore := func(absPath string) bool {
|
absShouldIgnore := func(absPath string) bool {
|
||||||
return ignore.ShouldIgnore(f.unrootedChecked(absPath, root))
|
rel, err := f.unrootedChecked(absPath, root)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return ignore.ShouldIgnore(rel)
|
||||||
}
|
}
|
||||||
err = notify.WatchWithFilter(watchPath, backendChan, absShouldIgnore, eventMask)
|
err = notify.WatchWithFilter(watchPath, backendChan, absShouldIgnore, eventMask)
|
||||||
} else {
|
} else {
|
||||||
@ -47,15 +51,16 @@ func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context
|
|||||||
if reachedMaxUserWatches(err) {
|
if reachedMaxUserWatches(err) {
|
||||||
err = errors.New("failed to setup inotify handler. Please increase inotify limits, see https://docs.syncthing.net/users/faq.html#inotify-limits")
|
err = errors.New("failed to setup inotify handler. Please increase inotify limits, see https://docs.syncthing.net/users/faq.html#inotify-limits")
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go f.watchLoop(name, root, backendChan, outChan, ignore, ctx)
|
errChan := make(chan error)
|
||||||
|
go f.watchLoop(name, root, backendChan, outChan, errChan, ignore, ctx)
|
||||||
|
|
||||||
return outChan, nil
|
return outChan, errChan, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *BasicFilesystem) watchLoop(name, evalRoot string, backendChan chan notify.EventInfo, outChan chan<- Event, ignore Matcher, ctx context.Context) {
|
func (f *BasicFilesystem) watchLoop(name, evalRoot string, backendChan chan notify.EventInfo, outChan chan<- Event, errChan chan<- error, ignore Matcher, ctx context.Context) {
|
||||||
for {
|
for {
|
||||||
// Detect channel overflow
|
// Detect channel overflow
|
||||||
if len(backendChan) == backendBuffer {
|
if len(backendChan) == backendBuffer {
|
||||||
@ -74,7 +79,18 @@ func (f *BasicFilesystem) watchLoop(name, evalRoot string, backendChan chan noti
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case ev := <-backendChan:
|
case ev := <-backendChan:
|
||||||
relPath := f.unrootedChecked(ev.Path(), evalRoot)
|
relPath, err := f.unrootedChecked(ev.Path(), evalRoot)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case errChan <- err:
|
||||||
|
l.Debugln(f.Type(), f.URI(), "Watch: Sending error", err)
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
notify.Stop(backendChan)
|
||||||
|
l.Debugln(f.Type(), f.URI(), "Watch: Stopped due to", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ignore.ShouldIgnore(relPath) {
|
if ignore.ShouldIgnore(relPath) {
|
||||||
l.Debugln(f.Type(), f.URI(), "Watch: Ignoring", relPath)
|
l.Debugln(f.Type(), f.URI(), "Watch: Ignoring", relPath)
|
||||||
continue
|
continue
|
||||||
|
@ -159,6 +159,7 @@ func TestWatchWinRoot(t *testing.T) {
|
|||||||
|
|
||||||
outChan := make(chan Event)
|
outChan := make(chan Event)
|
||||||
backendChan := make(chan notify.EventInfo, backendBuffer)
|
backendChan := make(chan notify.EventInfo, backendBuffer)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
@ -177,7 +178,7 @@ func TestWatchWinRoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
fs.watchLoop(".", root, backendChan, outChan, fakeMatcher{}, ctx)
|
fs.watchLoop(".", root, backendChan, outChan, errChan, fakeMatcher{}, ctx)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// filepath.Dir as watch has a /... suffix
|
// filepath.Dir as watch has a /... suffix
|
||||||
@ -192,6 +193,8 @@ func TestWatchWinRoot(t *testing.T) {
|
|||||||
if ev.Name != name {
|
if ev.Name != name {
|
||||||
t.Errorf("Unexpected event %v, expected %v", ev.Name, name)
|
t.Errorf("Unexpected event %v, expected %v", ev.Name, name)
|
||||||
}
|
}
|
||||||
|
case err := <-errChan:
|
||||||
|
t.Error("Received fatal watch error:", err)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,32 +203,22 @@ func TestWatchWinRoot(t *testing.T) {
|
|||||||
func TestWatchOutside(t *testing.T) {
|
func TestWatchOutside(t *testing.T) {
|
||||||
outChan := make(chan Event)
|
outChan := make(chan Event)
|
||||||
backendChan := make(chan notify.EventInfo, backendBuffer)
|
backendChan := make(chan notify.EventInfo, backendBuffer)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
// testFs is Filesystem, but we need BasicFilesystem here
|
// testFs is Filesystem, but we need BasicFilesystem here
|
||||||
fs := newBasicFilesystem(testDirAbs)
|
fs := newBasicFilesystem(testDirAbs)
|
||||||
|
|
||||||
go func() {
|
go fs.watchLoop(".", testDirAbs, backendChan, outChan, errChan, fakeMatcher{}, ctx)
|
||||||
defer func() {
|
|
||||||
if recover() == nil {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done(): // timed out
|
|
||||||
default:
|
|
||||||
t.Fatalf("Watch did not panic on receiving event outside of folder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
}()
|
|
||||||
fs.watchLoop(".", testDirAbs, backendChan, outChan, fakeMatcher{}, ctx)
|
|
||||||
}()
|
|
||||||
|
|
||||||
backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(testDirAbs), "outside"))
|
backendChan <- fakeEventInfo(filepath.Join(filepath.Dir(testDirAbs), "outside"))
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(10 * time.Second):
|
case <-time.After(10 * time.Second):
|
||||||
cancel()
|
cancel()
|
||||||
t.Errorf("Timed out before panicing")
|
t.Errorf("Timed out before receiving error")
|
||||||
|
case <-errChan:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,6 +226,7 @@ func TestWatchOutside(t *testing.T) {
|
|||||||
func TestWatchSubpath(t *testing.T) {
|
func TestWatchSubpath(t *testing.T) {
|
||||||
outChan := make(chan Event)
|
outChan := make(chan Event)
|
||||||
backendChan := make(chan notify.EventInfo, backendBuffer)
|
backendChan := make(chan notify.EventInfo, backendBuffer)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
@ -240,7 +234,7 @@ func TestWatchSubpath(t *testing.T) {
|
|||||||
fs := newBasicFilesystem(testDirAbs)
|
fs := newBasicFilesystem(testDirAbs)
|
||||||
|
|
||||||
abs, _ := fs.rooted("sub")
|
abs, _ := fs.rooted("sub")
|
||||||
go fs.watchLoop("sub", testDirAbs, backendChan, outChan, fakeMatcher{}, ctx)
|
go fs.watchLoop("sub", testDirAbs, backendChan, outChan, errChan, fakeMatcher{}, ctx)
|
||||||
|
|
||||||
backendChan <- fakeEventInfo(filepath.Join(abs, "file"))
|
backendChan <- fakeEventInfo(filepath.Join(abs, "file"))
|
||||||
|
|
||||||
@ -253,6 +247,8 @@ func TestWatchSubpath(t *testing.T) {
|
|||||||
if ev.Name != filepath.Join("sub", "file") {
|
if ev.Name != filepath.Join("sub", "file") {
|
||||||
t.Errorf("While watching a subfolder, received an event with unexpected path %v", ev.Name)
|
t.Errorf("While watching a subfolder, received an event with unexpected path %v", ev.Name)
|
||||||
}
|
}
|
||||||
|
case err := <-errChan:
|
||||||
|
t.Error("Received fatal watch error:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
@ -337,7 +333,7 @@ func TestWatchSymlinkedRoot(t *testing.T) {
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if _, err := linkedFs.Watch(".", fakeMatcher{}, ctx, false); err != nil {
|
if _, _, err := linkedFs.Watch(".", fakeMatcher{}, ctx, false); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,14 +346,10 @@ func TestWatchSymlinkedRoot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnrootedChecked(t *testing.T) {
|
func TestUnrootedChecked(t *testing.T) {
|
||||||
var unrooted string
|
|
||||||
defer func() {
|
|
||||||
if recover() == nil {
|
|
||||||
t.Fatal("unrootedChecked did not panic on outside path, but returned", unrooted)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
fs := newBasicFilesystem(testDirAbs)
|
fs := newBasicFilesystem(testDirAbs)
|
||||||
unrooted = fs.unrootedChecked("/random/other/path", testDirAbs)
|
if unrooted, err := fs.unrootedChecked("/random/other/path", testDirAbs); err == nil {
|
||||||
|
t.Error("unrootedChecked did not return an error on outside path, but returned", unrooted)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWatchIssue4877(t *testing.T) {
|
func TestWatchIssue4877(t *testing.T) {
|
||||||
@ -428,7 +420,7 @@ func testScenario(t *testing.T, name string, testCase func(), expectedEvents, al
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
eventChan, err := testFs.Watch(name, fm, ctx, false)
|
eventChan, errChan, err := testFs.Watch(name, fm, ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -439,8 +431,9 @@ func testScenario(t *testing.T, name string, testCase func(), expectedEvents, al
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case <-time.After(10 * time.Second):
|
case <-time.After(10 * time.Second):
|
||||||
t.Errorf("Timed out before receiving all expected events")
|
t.Error("Timed out before receiving all expected events")
|
||||||
|
case err := <-errChan:
|
||||||
|
t.Error("Received fatal watch error:", err)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,6 @@ package fs
|
|||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
func (f *BasicFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) {
|
func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||||
return nil, ErrWatchNotSupported
|
return nil, nil, ErrWatchNotSupported
|
||||||
}
|
}
|
||||||
|
@ -153,20 +153,20 @@ func (f *BasicFilesystem) Roots() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unrootedChecked returns the path relative to the folder root (same as
|
// unrootedChecked returns the path relative to the folder root (same as
|
||||||
// unrooted). It panics if the given path is not a subpath and handles the
|
// unrooted) or an error if the given path is not a subpath and handles the
|
||||||
// special case when the given path is the folder root without a trailing
|
// special case when the given path is the folder root without a trailing
|
||||||
// pathseparator.
|
// pathseparator.
|
||||||
func (f *BasicFilesystem) unrootedChecked(absPath, root string) string {
|
func (f *BasicFilesystem) unrootedChecked(absPath, root string) (string, error) {
|
||||||
absPath = f.resolveWin83(absPath)
|
absPath = f.resolveWin83(absPath)
|
||||||
lowerAbsPath := UnicodeLowercase(absPath)
|
lowerAbsPath := UnicodeLowercase(absPath)
|
||||||
lowerRoot := UnicodeLowercase(root)
|
lowerRoot := UnicodeLowercase(root)
|
||||||
if lowerAbsPath+string(PathSeparator) == lowerRoot {
|
if lowerAbsPath+string(PathSeparator) == lowerRoot {
|
||||||
return "."
|
return ".", nil
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(lowerAbsPath, lowerRoot) {
|
if !strings.HasPrefix(lowerAbsPath, lowerRoot) {
|
||||||
panic(fmt.Sprintf("bug: Notify backend is processing a change outside of the filesystem root: f.root==%v, root==%v (lower), path==%v (lower)", f.root, lowerRoot, lowerAbsPath))
|
return "", f.newErrWatchEventOutsideRoot(lowerAbsPath, lowerRoot)
|
||||||
}
|
}
|
||||||
return rel(absPath, root)
|
return rel(absPath, root), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rel(path, prefix string) string {
|
func rel(path, prefix string) string {
|
||||||
|
@ -127,19 +127,15 @@ func TestRelUnrootedCheckedWindows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unrootedChecked really just wraps rel, and does not care about
|
// unrootedChecked really just wraps rel, and does not care about
|
||||||
// the actual root of that filesystem, but should not panic on these
|
// the actual root of that filesystem, but should not return an error
|
||||||
// test cases.
|
// on these test cases.
|
||||||
fs := BasicFilesystem{root: tc.root}
|
for _, root := range []string{tc.root, strings.ToLower(tc.root), strings.ToUpper(tc.root)} {
|
||||||
if res := fs.unrootedChecked(tc.abs, tc.root); res != tc.expectedRel {
|
fs := BasicFilesystem{root: root}
|
||||||
t.Errorf(`unrootedChecked("%v", "%v") == "%v", expected "%v"`, tc.abs, tc.root, res, tc.expectedRel)
|
if res, err := fs.unrootedChecked(tc.abs, tc.root); err != nil {
|
||||||
|
t.Errorf(`Unexpected error from unrootedChecked("%v", "%v"): %v (fs.root: %v)`, tc.abs, tc.root, err, root)
|
||||||
|
} else if res != tc.expectedRel {
|
||||||
|
t.Errorf(`unrootedChecked("%v", "%v") == "%v", expected "%v" (fs.root: %v)`, tc.abs, tc.root, res, tc.expectedRel, root)
|
||||||
}
|
}
|
||||||
fs = BasicFilesystem{root: strings.ToLower(tc.root)}
|
|
||||||
if res := fs.unrootedChecked(tc.abs, tc.root); res != tc.expectedRel {
|
|
||||||
t.Errorf(`unrootedChecked("%v", "%v") == "%v", expected "%v"`, tc.abs, tc.root, res, tc.expectedRel)
|
|
||||||
}
|
|
||||||
fs = BasicFilesystem{root: strings.ToUpper(tc.root)}
|
|
||||||
if res := fs.unrootedChecked(tc.abs, tc.root); res != tc.expectedRel {
|
|
||||||
t.Errorf(`unrootedChecked("%v", "%v") == "%v", expected "%v"`, tc.abs, tc.root, res, tc.expectedRel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,6 @@ func (fs *errorFilesystem) Usage(name string) (Usage, error)
|
|||||||
func (fs *errorFilesystem) Type() FilesystemType { return fs.fsType }
|
func (fs *errorFilesystem) Type() FilesystemType { return fs.fsType }
|
||||||
func (fs *errorFilesystem) URI() string { return fs.uri }
|
func (fs *errorFilesystem) URI() string { return fs.uri }
|
||||||
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
|
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
|
||||||
func (fs *errorFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) {
|
func (fs *errorFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||||
return nil, fs.err
|
return nil, nil, fs.err
|
||||||
}
|
}
|
||||||
|
@ -466,8 +466,8 @@ func (fs *fakefs) Walk(name string, walkFn WalkFunc) error {
|
|||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *fakefs) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) {
|
func (fs *fakefs) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||||
return nil, ErrWatchNotSupported
|
return nil, nil, ErrWatchNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *fakefs) Hide(name string) error {
|
func (fs *fakefs) Hide(name string) error {
|
||||||
|
@ -36,7 +36,10 @@ type Filesystem interface {
|
|||||||
Stat(name string) (FileInfo, error)
|
Stat(name string) (FileInfo, error)
|
||||||
SymlinksSupported() bool
|
SymlinksSupported() bool
|
||||||
Walk(name string, walkFn WalkFunc) error
|
Walk(name string, walkFn WalkFunc) error
|
||||||
Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error)
|
// If setup fails, returns non-nil error, and if afterwards a fatal (!)
|
||||||
|
// error occurs, sends that error on the channel. Afterwards this watch
|
||||||
|
// can be considered stopped.
|
||||||
|
Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error)
|
||||||
Hide(name string) error
|
Hide(name string) error
|
||||||
Unhide(name string) error
|
Unhide(name string) error
|
||||||
Glob(pattern string) ([]string, error)
|
Glob(pattern string) ([]string, error)
|
||||||
|
@ -128,10 +128,10 @@ func (fs *logFilesystem) Walk(root string, walkFn WalkFunc) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *logFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) {
|
func (fs *logFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
|
||||||
evChan, err := fs.Filesystem.Watch(path, ignore, ctx, ignorePerms)
|
evChan, errChan, err := fs.Filesystem.Watch(path, ignore, ctx, ignorePerms)
|
||||||
l.Debugln(getCaller(), fs.Type(), fs.URI(), "Watch", path, ignore, ignorePerms, err)
|
l.Debugln(getCaller(), fs.Type(), fs.URI(), "Watch", path, ignore, ignorePerms, err)
|
||||||
return evChan, err
|
return evChan, errChan, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *logFilesystem) Unhide(name string) error {
|
func (fs *logFilesystem) Unhide(name string) error {
|
||||||
|
@ -173,9 +173,6 @@ func (f *folder) Serve() {
|
|||||||
pullFailTimer.Reset(pause)
|
pullFailTimer.Reset(pause)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The reason for running the scanner from within the puller is that
|
|
||||||
// this is the easiest way to make sure we are not doing both at the
|
|
||||||
// same time.
|
|
||||||
case <-f.scanTimer.C:
|
case <-f.scanTimer.C:
|
||||||
l.Debugln(f, "Scanning subdirectories")
|
l.Debugln(f, "Scanning subdirectories")
|
||||||
f.scanTimerFired()
|
f.scanTimerFired()
|
||||||
@ -611,17 +608,56 @@ func (f *folder) startWatch() {
|
|||||||
f.watchChan = make(chan []string)
|
f.watchChan = make(chan []string)
|
||||||
f.watchCancel = cancel
|
f.watchCancel = cancel
|
||||||
f.watchMut.Unlock()
|
f.watchMut.Unlock()
|
||||||
go f.startWatchAsync(ctx)
|
go f.monitorWatch(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startWatchAsync tries to start the filesystem watching and retries every minute on failure.
|
// monitorWatch starts the filesystem watching and retries every minute on failure.
|
||||||
// It is a convenience function that should not be used except in startWatch.
|
// It should not be used except in startWatch.
|
||||||
func (f *folder) startWatchAsync(ctx context.Context) {
|
func (f *folder) monitorWatch(ctx context.Context) {
|
||||||
timer := time.NewTimer(0)
|
failTimer := time.NewTimer(0)
|
||||||
|
aggrCtx, aggrCancel := context.WithCancel(ctx)
|
||||||
|
var err error
|
||||||
|
var eventChan <-chan fs.Event
|
||||||
|
var errChan <-chan error
|
||||||
|
warnedOutside := false
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-failTimer.C:
|
||||||
eventChan, err := f.Filesystem().Watch(".", f.ignores, ctx, f.IgnorePerms)
|
eventChan, errChan, err = f.Filesystem().Watch(".", f.ignores, ctx, f.IgnorePerms)
|
||||||
|
// We do this at most once per minute which is the
|
||||||
|
// default rescan time without watcher.
|
||||||
|
f.scanOnWatchErr()
|
||||||
|
f.setWatchError(err)
|
||||||
|
if err != nil {
|
||||||
|
failTimer.Reset(time.Minute)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
watchaggregator.Aggregate(eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, aggrCtx)
|
||||||
|
l.Debugln("Started filesystem watcher for folder", f.Description())
|
||||||
|
case err = <-errChan:
|
||||||
|
f.setWatchError(err)
|
||||||
|
// This error was previously a panic and should never occur, so generate
|
||||||
|
// a warning, but don't do it repetitively.
|
||||||
|
if !warnedOutside {
|
||||||
|
if _, ok := err.(*fs.ErrWatchEventOutsideRoot); ok {
|
||||||
|
l.Warnln(err)
|
||||||
|
warnedOutside = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aggrCancel()
|
||||||
|
errChan = nil
|
||||||
|
aggrCtx, aggrCancel = context.WithCancel(ctx)
|
||||||
|
failTimer.Reset(time.Minute)
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setWatchError sets the current error state of the watch and should be called
|
||||||
|
// regardless of whether err is nil or not.
|
||||||
|
func (f *folder) setWatchError(err error) {
|
||||||
f.watchMut.Lock()
|
f.watchMut.Lock()
|
||||||
prevErr := f.watchErr
|
prevErr := f.watchErr
|
||||||
f.watchErr = err
|
f.watchErr = err
|
||||||
@ -638,24 +674,23 @@ func (f *folder) startWatchAsync(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
events.Default.Log(events.FolderWatchStateChanged, data)
|
events.Default.Log(events.FolderWatchStateChanged, data)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if prevErr == errWatchNotStarted {
|
if prevErr == errWatchNotStarted {
|
||||||
l.Infof("Error while trying to start filesystem watcher for folder %s, trying again in 1min: %v", f.Description(), err)
|
l.Infof("Error while trying to start filesystem watcher for folder %s, trying again in 1min: %v", f.Description(), err)
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
l.Debugf("Repeat error while trying to start filesystem watcher for folder %s, trying again in 1min: %v", f.Description(), err)
|
l.Debugf("Repeat error while trying to start filesystem watcher for folder %s, trying again in 1min: %v", f.Description(), err)
|
||||||
}
|
}
|
||||||
timer.Reset(time.Minute)
|
|
||||||
continue
|
// scanOnWatchErr schedules a full scan immediately if an error occurred while watching.
|
||||||
}
|
func (f *folder) scanOnWatchErr() {
|
||||||
f.watchMut.Lock()
|
f.watchMut.Lock()
|
||||||
defer f.watchMut.Unlock()
|
if f.watchErr != nil && f.watchErr != errWatchNotStarted {
|
||||||
watchaggregator.Aggregate(eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, ctx)
|
f.Delay(0)
|
||||||
l.Debugln("Started filesystem watcher for folder", f.Description())
|
|
||||||
return
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
f.watchMut.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *folder) setError(err error) {
|
func (f *folder) setError(err error) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user