watch: fix spurious errors while watching (#1726)

This commit is contained in:
Nick Santos 2019-06-07 18:03:02 -04:00 committed by Nicolas De loof
parent f82e2de57e
commit 5e0f1eec16
3 changed files with 98 additions and 9 deletions

View File

@ -1,6 +1,8 @@
package watch package watch
import ( import (
"bytes"
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -10,6 +12,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/windmilleng/tilt/internal/logger"
"github.com/windmilleng/tilt/internal/testutils/tempdir" "github.com/windmilleng/tilt/internal/testutils/tempdir"
) )
@ -54,6 +58,65 @@ func TestEventOrdering(t *testing.T) {
f.assertEvents(expected...) f.assertEvents(expected...)
} }
// Simulate a git branch switch that creates a bunch
// of directories, creates files in them, then deletes
// them all quickly. Make sure there are no errors.
func TestGitBranchSwitch(t *testing.T) {
f := newNotifyFixture(t)
defer f.tearDown()
count := 10
dirs := make([]string, count)
for i, _ := range dirs {
dir := f.TempDir("watched")
dirs[i] = dir
err := f.notify.Add(dir)
if err != nil {
t.Fatal(err)
}
}
f.fsync()
f.events = nil
// consume all the events in the background
ctx, cancel := context.WithCancel(context.Background())
done := f.consumeEventsInBackground(ctx)
for i, dir := range dirs {
for j := 0; j < count; j++ {
base := fmt.Sprintf("x/y/dir-%d/x.txt", j)
p := filepath.Join(dir, base)
f.WriteFile(p, "contents")
}
if i != 0 {
os.RemoveAll(dir)
}
}
cancel()
err := <-done
if err != nil {
t.Fatal(err)
}
f.fsync()
f.events = nil
// Make sure the watch on the first dir still works.
dir := dirs[0]
path := filepath.Join(dir, "change")
f.WriteFile(path, "hello\n")
f.fsync()
f.assertEvents(path)
// Make sure there are no errors in the out stream
assert.Equal(t, "", f.out.String())
}
func TestWatchesAreRecursive(t *testing.T) { func TestWatchesAreRecursive(t *testing.T) {
f := newNotifyFixture(t) f := newNotifyFixture(t)
defer f.tearDown() defer f.tearDown()
@ -412,6 +475,7 @@ func TestWatchNonexistentDirectory(t *testing.T) {
// } // }
type notifyFixture struct { type notifyFixture struct {
out *bytes.Buffer
*tempdir.TempDirFixture *tempdir.TempDirFixture
notify Notify notify Notify
watched string watched string
@ -419,7 +483,8 @@ type notifyFixture struct {
} }
func newNotifyFixture(t *testing.T) *notifyFixture { func newNotifyFixture(t *testing.T) *notifyFixture {
notify, err := NewWatcher() out := bytes.NewBuffer(nil)
notify, err := NewWatcher(logger.NewLogger(logger.DebugLvl, out))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -435,6 +500,7 @@ func newNotifyFixture(t *testing.T) *notifyFixture {
TempDirFixture: f, TempDirFixture: f,
watched: watched, watched: watched,
notify: notify, notify: notify,
out: out,
} }
} }
@ -460,6 +526,25 @@ func (f *notifyFixture) assertEvents(expected ...string) {
} }
} }
func (f *notifyFixture) consumeEventsInBackground(ctx context.Context) chan error {
done := make(chan error)
go func() {
for {
select {
case <-ctx.Done():
close(done)
return
case err := <-f.notify.Errors():
done <- err
close(done)
return
case <-f.notify.Events():
}
}
}()
return done
}
func (f *notifyFixture) fsync() { func (f *notifyFixture) fsync() {
syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano()) syncPathBase := fmt.Sprintf("sync-%d.txt", time.Now().UnixNano())
syncPath := filepath.Join(f.watched, syncPathBase) syncPath := filepath.Join(f.watched, syncPathBase)

View File

@ -5,6 +5,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/windmilleng/tilt/internal/logger"
"github.com/windmilleng/tilt/internal/ospath" "github.com/windmilleng/tilt/internal/ospath"
"github.com/windmilleng/fsevents" "github.com/windmilleng/fsevents"
@ -120,7 +121,7 @@ func (d *darwinNotify) Errors() chan error {
return d.errors return d.errors
} }
func NewWatcher() (Notify, error) { func NewWatcher(l logger.Logger) (Notify, error) {
dw := &darwinNotify{ dw := &darwinNotify{
stream: &fsevents.EventStream{ stream: &fsevents.EventStream{
Latency: 1 * time.Millisecond, Latency: 1 * time.Millisecond,

View File

@ -4,7 +4,6 @@ package watch
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
@ -12,6 +11,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/windmilleng/fsnotify" "github.com/windmilleng/fsnotify"
"github.com/windmilleng/tilt/internal/logger"
"github.com/windmilleng/tilt/internal/ospath" "github.com/windmilleng/tilt/internal/ospath"
) )
@ -20,6 +20,7 @@ import (
// //
// All OS-specific codepaths are handled by fsnotify. // All OS-specific codepaths are handled by fsnotify.
type naiveNotify struct { type naiveNotify struct {
log logger.Logger
watcher *fsnotify.Watcher watcher *fsnotify.Watcher
events chan fsnotify.Event events chan fsnotify.Event
wrappedEvents chan FileEvent wrappedEvents chan FileEvent
@ -127,7 +128,7 @@ func (d *naiveNotify) loop() {
} }
// TODO(dbentley): if there's a delete should we call d.watcher.Remove to prevent leaking? // TODO(dbentley): if there's a delete should we call d.watcher.Remove to prevent leaking?
if err := filepath.Walk(e.Name, func(path string, mode os.FileInfo, err error) error { err := filepath.Walk(e.Name, func(path string, mode os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
} }
@ -151,13 +152,14 @@ func (d *naiveNotify) loop() {
} }
if shouldWatch { if shouldWatch {
err := d.watcher.Add(path) err := d.watcher.Add(path)
if err != nil { if err != nil && !os.IsNotExist(err) {
log.Printf("Error watching path %s: %s", e.Name, err) d.log.Infof("Error watching path %s: %s", e.Name, err)
} }
} }
return nil return nil
}); err != nil { })
log.Printf("Error walking directory %s: %s", e.Name, err) if err != nil && !os.IsNotExist(err) {
d.log.Infof("Error walking directory %s: %s", e.Name, err)
} }
} }
} }
@ -177,7 +179,7 @@ func (d *naiveNotify) shouldNotify(path string) bool {
return false return false
} }
func NewWatcher() (*naiveNotify, error) { func NewWatcher(l logger.Logger) (*naiveNotify, error) {
fsw, err := fsnotify.NewWatcher() fsw, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
return nil, err return nil, err
@ -186,6 +188,7 @@ func NewWatcher() (*naiveNotify, error) {
wrappedEvents := make(chan FileEvent) wrappedEvents := make(chan FileEvent)
wmw := &naiveNotify{ wmw := &naiveNotify{
log: l,
watcher: fsw, watcher: fsw,
events: fsw.Events, events: fsw.Events,
wrappedEvents: wrappedEvents, wrappedEvents: wrappedEvents,