Skip to content

Commit f82e2de

Browse files
dbentleyndeloof
authored andcommitted
watch: don't watch each individual file (docker#1613)
1 parent 9c7f7bc commit f82e2de

File tree

4 files changed

+241
-71
lines changed

4 files changed

+241
-71
lines changed

pkg/watch/features.go

Lines changed: 0 additions & 27 deletions
This file was deleted.

pkg/watch/notify_test.go

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io/ioutil"
66
"os"
77
"path/filepath"
8+
"runtime"
89
"strings"
910
"testing"
1011
"time"
@@ -329,7 +330,7 @@ func TestWatchBothDirAndFile(t *testing.T) {
329330
f.assertEvents(fileB)
330331
}
331332

332-
func TestWatchNonexistentDirectory(t *testing.T) {
333+
func TestWatchNonexistentFileInNonexistentDirectoryCreatedSimultaneously(t *testing.T) {
333334
f := newNotifyFixture(t)
334335
defer f.tearDown()
335336

@@ -347,6 +348,69 @@ func TestWatchNonexistentDirectory(t *testing.T) {
347348
f.assertEvents(file)
348349
}
349350

351+
func TestWatchNonexistentDirectory(t *testing.T) {
352+
f := newNotifyFixture(t)
353+
defer f.tearDown()
354+
355+
root := f.JoinPath("root")
356+
err := os.Mkdir(root, 0777)
357+
if err != nil {
358+
t.Fatal(err)
359+
}
360+
parent := f.JoinPath("parent")
361+
file := f.JoinPath("parent", "a")
362+
363+
f.watch(parent)
364+
f.fsync()
365+
f.events = nil
366+
367+
err = os.Mkdir(parent, 0777)
368+
if err != nil {
369+
t.Fatal(err)
370+
}
371+
372+
if runtime.GOOS == "darwin" {
373+
// for directories that were the root of an Add, we don't report creation, cf. watcher_darwin.go
374+
f.assertEvents()
375+
} else {
376+
f.assertEvents(parent)
377+
}
378+
f.WriteFile(file, "hello")
379+
380+
if runtime.GOOS == "darwin" {
381+
// mac doesn't return the dir change as part of file creation
382+
f.assertEvents(file)
383+
} else {
384+
f.assertEvents(parent, file)
385+
}
386+
}
387+
388+
// doesn't work on linux
389+
// func TestWatchNonexistentFileInNonexistentDirectory(t *testing.T) {
390+
// f := newNotifyFixture(t)
391+
// defer f.tearDown()
392+
393+
// root := f.JoinPath("root")
394+
// err := os.Mkdir(root, 0777)
395+
// if err != nil {
396+
// t.Fatal(err)
397+
// }
398+
// parent := f.JoinPath("parent")
399+
// file := f.JoinPath("parent", "a")
400+
401+
// f.watch(file)
402+
// f.assertEvents()
403+
404+
// err = os.Mkdir(parent, 0777)
405+
// if err != nil {
406+
// t.Fatal(err)
407+
// }
408+
409+
// f.assertEvents()
410+
// f.WriteFile(file, "hello")
411+
// f.assertEvents(file)
412+
// }
413+
350414
type notifyFixture struct {
351415
*tempdir.TempDirFixture
352416
notify Notify
@@ -355,7 +419,6 @@ type notifyFixture struct {
355419
}
356420

357421
func newNotifyFixture(t *testing.T) *notifyFixture {
358-
SetLimitChecksEnabled(false)
359422
notify, err := NewWatcher()
360423
if err != nil {
361424
t.Fatal(err)
@@ -434,12 +497,20 @@ F:
434497
}
435498

436499
func (f *notifyFixture) tearDown() {
437-
SetLimitChecksEnabled(true)
438-
439500
err := f.notify.Close()
440501
if err != nil {
441502
f.T().Fatal(err)
442503
}
443504

505+
// drain channels from watcher
506+
go func() {
507+
for _ = range f.notify.Events() {
508+
}
509+
}()
510+
go func() {
511+
for _ = range f.notify.Errors() {
512+
}
513+
}()
514+
444515
f.TempDirFixture.TearDown()
445516
}

pkg/watch/watcher_naive.go

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"log"
88
"os"
99
"path/filepath"
10+
"sync"
1011

1112
"github.com/pkg/errors"
1213
"github.com/windmilleng/fsnotify"
@@ -24,6 +25,8 @@ type naiveNotify struct {
2425
wrappedEvents chan FileEvent
2526
errors chan error
2627

28+
mu sync.Mutex
29+
2730
// Paths that we're watching that should be passed up to the caller.
2831
// Note that we may have to watch ancestors of these paths
2932
// in order to fulfill the API promise.
@@ -48,11 +51,14 @@ func (d *naiveNotify) Add(name string) error {
4851
return errors.Wrapf(err, "notify.Add(%q)", name)
4952
}
5053
} else {
51-
err = d.watcher.Add(name)
54+
err = d.watcher.Add(filepath.Dir(name))
5255
if err != nil {
53-
return errors.Wrapf(err, "notify.Add(%q)", name)
56+
return errors.Wrapf(err, "notify.Add(%q)", filepath.Dir(name))
5457
}
5558
}
59+
60+
d.mu.Lock()
61+
defer d.mu.Unlock()
5662
d.notifyList[name] = true
5763

5864
return nil
@@ -64,6 +70,9 @@ func (d *naiveNotify) watchRecursively(dir string) error {
6470
return err
6571
}
6672

73+
if !mode.IsDir() {
74+
return nil
75+
}
6776
err = d.watcher.Add(path)
6877
if err != nil {
6978
if os.IsNotExist(err) {
@@ -106,56 +115,63 @@ func (d *naiveNotify) Errors() chan error {
106115
}
107116

108117
func (d *naiveNotify) loop() {
118+
defer close(d.wrappedEvents)
109119
for e := range d.events {
110-
isCreateOp := e.Op&fsnotify.Create == fsnotify.Create
111-
shouldWalk := false
112-
if isCreateOp {
113-
isDir, err := isDir(e.Name)
114-
if err != nil {
115-
log.Printf("Error stat-ing file %s: %s", e.Name, err)
116-
continue
120+
shouldNotify := d.shouldNotify(e.Name)
121+
122+
if e.Op&fsnotify.Create != fsnotify.Create {
123+
if shouldNotify {
124+
d.wrappedEvents <- FileEvent{e.Name}
117125
}
118-
shouldWalk = isDir
126+
continue
119127
}
120-
if shouldWalk {
121-
err := filepath.Walk(e.Name, func(path string, mode os.FileInfo, err error) error {
122-
if err != nil {
123-
return err
124-
}
125-
newE := fsnotify.Event{
126-
Op: fsnotify.Create,
127-
Name: path,
128-
}
129128

130-
if d.shouldNotify(newE) {
131-
d.wrappedEvents <- FileEvent{newE.Name}
129+
// TODO(dbentley): if there's a delete should we call d.watcher.Remove to prevent leaking?
130+
if err := filepath.Walk(e.Name, func(path string, mode os.FileInfo, err error) error {
131+
if err != nil {
132+
return err
133+
}
132134

133-
// TODO(dmiller): symlinks 😭
134-
err = d.Add(path)
135-
if err != nil {
136-
log.Printf("Error watching path %s: %s", e.Name, err)
137-
}
135+
if d.shouldNotify(path) {
136+
d.wrappedEvents <- FileEvent{path}
137+
}
138+
139+
// TODO(dmiller): symlinks 😭
140+
141+
shouldWatch := false
142+
if mode.IsDir() {
143+
// watch all directories
144+
shouldWatch = true
145+
} else {
146+
// watch files that are explicitly named, but don't watch others
147+
_, ok := d.notifyList[path]
148+
if ok {
149+
shouldWatch = true
150+
}
151+
}
152+
if shouldWatch {
153+
err := d.watcher.Add(path)
154+
if err != nil {
155+
log.Printf("Error watching path %s: %s", e.Name, err)
138156
}
139-
return nil
140-
})
141-
if err != nil {
142-
log.Printf("Error walking directory %s: %s", e.Name, err)
143157
}
144-
} else if d.shouldNotify(e) {
145-
d.wrappedEvents <- FileEvent{e.Name}
158+
return nil
159+
}); err != nil {
160+
log.Printf("Error walking directory %s: %s", e.Name, err)
146161
}
147162
}
148163
}
149164

150-
func (d *naiveNotify) shouldNotify(e fsnotify.Event) bool {
151-
if _, ok := d.notifyList[e.Name]; ok {
165+
func (d *naiveNotify) shouldNotify(path string) bool {
166+
d.mu.Lock()
167+
defer d.mu.Unlock()
168+
if _, ok := d.notifyList[path]; ok {
152169
return true
153-
} else {
154-
// TODO(dmiller): maybe use a prefix tree here?
155-
for path := range d.notifyList {
156-
if ospath.IsChild(path, e.Name) {
157-
return true
158-
}
170+
}
171+
// TODO(dmiller): maybe use a prefix tree here?
172+
for root := range d.notifyList {
173+
if ospath.IsChild(root, path) {
174+
return true
159175
}
160176
}
161177
return false

0 commit comments

Comments
 (0)