Skip to content

Commit 38b3f3b

Browse files
Dan Millerndeloof
authored andcommitted
watch: use sinceWhen and HistoryDone to avoid spurious events (docker#557)
Here's our new watch strategy on Darwin in a nutshell: 1. Create an fsevents stream for events "since" the last event ID that we saw globally. 2. Add a path that we want to watch 3. Add that path to a map of paths that we're watching _directly_. 4. Restart the event stream to pick up the new path. 5. Ignore all events for all watches until we've seen a `HistoryDone` event. 6. Ignore the first `ItemCreated` event for paths we're watching that are also directories 7. Otherwise, forward along all events.
1 parent c5bce8b commit 38b3f3b

File tree

2 files changed

+57
-17
lines changed

2 files changed

+57
-17
lines changed

pkg/watch/notify_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,40 @@ func TestWatchBrokenLink(t *testing.T) {
303303
f.assertEvents(link)
304304
}
305305

306+
func TestMoveAndReplace(t *testing.T) {
307+
f := newNotifyFixture(t)
308+
defer f.tearDown()
309+
310+
root, err := f.root.NewDir("root")
311+
if err != nil {
312+
t.Fatal(err)
313+
}
314+
315+
file := filepath.Join(root.Path(), "myfile")
316+
err = ioutil.WriteFile(file, []byte("hello"), 0777)
317+
if err != nil {
318+
t.Fatal(err)
319+
}
320+
321+
err = f.notify.Add(file)
322+
if err != nil {
323+
t.Fatal(err)
324+
}
325+
326+
tmpFile := filepath.Join(root.Path(), ".myfile.swp")
327+
err = ioutil.WriteFile(tmpFile, []byte("world"), 0777)
328+
if err != nil {
329+
t.Fatal(err)
330+
}
331+
332+
err = os.Rename(tmpFile, file)
333+
if err != nil {
334+
t.Fatal(err)
335+
}
336+
337+
f.assertEvents(file)
338+
}
339+
306340
type notifyFixture struct {
307341
t *testing.T
308342
root *TempDir

pkg/watch/watcher_darwin.go

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ type darwinNotify struct {
2121
// change.
2222
sm *sync.Mutex
2323

24-
// When a watch is created for a directory, we've seen fsevents non-determistically
25-
// fire 0-3 CREATE events for that directory. We want to ignore these.
26-
ignoreCreatedEvents map[string]bool
24+
pathsWereWatching map[string]interface{}
25+
sawAnyHistoryDone bool
2726
}
2827

2928
func (d *darwinNotify) loop() {
@@ -39,20 +38,24 @@ func (d *darwinNotify) loop() {
3938
for _, e := range events {
4039
e.Path = filepath.Join("/", e.Path)
4140

42-
if e.Flags&fsevents.ItemCreated == fsevents.ItemCreated {
41+
if e.Flags&fsevents.HistoryDone == fsevents.HistoryDone {
4342
d.sm.Lock()
44-
shouldIgnore := d.ignoreCreatedEvents[e.Path]
45-
if !shouldIgnore {
46-
// If we got a created event for something
47-
// that's not on the ignore list, we assume
48-
// we're done with the spurious events.
49-
d.ignoreCreatedEvents = nil
50-
}
43+
d.sawAnyHistoryDone = true
5144
d.sm.Unlock()
45+
continue
46+
}
47+
48+
// We wait until we've seen the HistoryDone event for this watcher before processing any events
49+
// so that we skip all of the "spurious" events that precede it.
50+
if !d.sawAnyHistoryDone {
51+
continue
52+
}
5253

53-
if shouldIgnore {
54-
continue
55-
}
54+
_, isPathWereWatching := d.pathsWereWatching[e.Path]
55+
if e.Flags&fsevents.ItemIsDir == fsevents.ItemIsDir && e.Flags&fsevents.ItemCreated == fsevents.ItemCreated && isPathWereWatching {
56+
// This is the first create for the path that we're watching. We always get exactly one of these
57+
// even after we get the HistoryDone event. Skip it.
58+
continue
5659
}
5760

5861
d.events <- FileEvent{
@@ -80,10 +83,10 @@ func (d *darwinNotify) Add(name string) error {
8083

8184
es.Paths = append(es.Paths, name)
8285

83-
if d.ignoreCreatedEvents == nil {
84-
d.ignoreCreatedEvents = make(map[string]bool, 1)
86+
if d.pathsWereWatching == nil {
87+
d.pathsWereWatching = make(map[string]interface{})
8588
}
86-
d.ignoreCreatedEvents[name] = true
89+
d.pathsWereWatching[name] = struct{}{}
8790

8891
if len(es.Paths) == 1 {
8992
es.Start()
@@ -119,6 +122,9 @@ func NewWatcher() (Notify, error) {
119122
stream: &fsevents.EventStream{
120123
Latency: 1 * time.Millisecond,
121124
Flags: fsevents.FileEvents,
125+
// NOTE(dmiller): this corresponds to the `sinceWhen` parameter in FSEventStreamCreate
126+
// https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
127+
EventID: fsevents.LatestEventID(),
122128
},
123129
sm: &sync.Mutex{},
124130
events: make(chan FileEvent),

0 commit comments

Comments
 (0)