mirror of
https://github.com/docker/compose.git
synced 2025-07-27 07:34:10 +02:00
watch: use sinceWhen
and HistoryDone
to avoid spurious events (#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.
This commit is contained in:
parent
c5bce8bd42
commit
38b3f3b678
@ -303,6 +303,40 @@ func TestWatchBrokenLink(t *testing.T) {
|
|||||||
f.assertEvents(link)
|
f.assertEvents(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMoveAndReplace(t *testing.T) {
|
||||||
|
f := newNotifyFixture(t)
|
||||||
|
defer f.tearDown()
|
||||||
|
|
||||||
|
root, err := f.root.NewDir("root")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file := filepath.Join(root.Path(), "myfile")
|
||||||
|
err = ioutil.WriteFile(file, []byte("hello"), 0777)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.notify.Add(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile := filepath.Join(root.Path(), ".myfile.swp")
|
||||||
|
err = ioutil.WriteFile(tmpFile, []byte("world"), 0777)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Rename(tmpFile, file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.assertEvents(file)
|
||||||
|
}
|
||||||
|
|
||||||
type notifyFixture struct {
|
type notifyFixture struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
root *TempDir
|
root *TempDir
|
||||||
|
@ -21,9 +21,8 @@ type darwinNotify struct {
|
|||||||
// change.
|
// change.
|
||||||
sm *sync.Mutex
|
sm *sync.Mutex
|
||||||
|
|
||||||
// When a watch is created for a directory, we've seen fsevents non-determistically
|
pathsWereWatching map[string]interface{}
|
||||||
// fire 0-3 CREATE events for that directory. We want to ignore these.
|
sawAnyHistoryDone bool
|
||||||
ignoreCreatedEvents map[string]bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *darwinNotify) loop() {
|
func (d *darwinNotify) loop() {
|
||||||
@ -39,20 +38,24 @@ func (d *darwinNotify) loop() {
|
|||||||
for _, e := range events {
|
for _, e := range events {
|
||||||
e.Path = filepath.Join("/", e.Path)
|
e.Path = filepath.Join("/", e.Path)
|
||||||
|
|
||||||
if e.Flags&fsevents.ItemCreated == fsevents.ItemCreated {
|
if e.Flags&fsevents.HistoryDone == fsevents.HistoryDone {
|
||||||
d.sm.Lock()
|
d.sm.Lock()
|
||||||
shouldIgnore := d.ignoreCreatedEvents[e.Path]
|
d.sawAnyHistoryDone = true
|
||||||
if !shouldIgnore {
|
|
||||||
// If we got a created event for something
|
|
||||||
// that's not on the ignore list, we assume
|
|
||||||
// we're done with the spurious events.
|
|
||||||
d.ignoreCreatedEvents = nil
|
|
||||||
}
|
|
||||||
d.sm.Unlock()
|
d.sm.Unlock()
|
||||||
|
|
||||||
if shouldIgnore {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We wait until we've seen the HistoryDone event for this watcher before processing any events
|
||||||
|
// so that we skip all of the "spurious" events that precede it.
|
||||||
|
if !d.sawAnyHistoryDone {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, isPathWereWatching := d.pathsWereWatching[e.Path]
|
||||||
|
if e.Flags&fsevents.ItemIsDir == fsevents.ItemIsDir && e.Flags&fsevents.ItemCreated == fsevents.ItemCreated && isPathWereWatching {
|
||||||
|
// This is the first create for the path that we're watching. We always get exactly one of these
|
||||||
|
// even after we get the HistoryDone event. Skip it.
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
d.events <- FileEvent{
|
d.events <- FileEvent{
|
||||||
@ -80,10 +83,10 @@ func (d *darwinNotify) Add(name string) error {
|
|||||||
|
|
||||||
es.Paths = append(es.Paths, name)
|
es.Paths = append(es.Paths, name)
|
||||||
|
|
||||||
if d.ignoreCreatedEvents == nil {
|
if d.pathsWereWatching == nil {
|
||||||
d.ignoreCreatedEvents = make(map[string]bool, 1)
|
d.pathsWereWatching = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
d.ignoreCreatedEvents[name] = true
|
d.pathsWereWatching[name] = struct{}{}
|
||||||
|
|
||||||
if len(es.Paths) == 1 {
|
if len(es.Paths) == 1 {
|
||||||
es.Start()
|
es.Start()
|
||||||
@ -119,6 +122,9 @@ func NewWatcher() (Notify, error) {
|
|||||||
stream: &fsevents.EventStream{
|
stream: &fsevents.EventStream{
|
||||||
Latency: 1 * time.Millisecond,
|
Latency: 1 * time.Millisecond,
|
||||||
Flags: fsevents.FileEvents,
|
Flags: fsevents.FileEvents,
|
||||||
|
// NOTE(dmiller): this corresponds to the `sinceWhen` parameter in FSEventStreamCreate
|
||||||
|
// https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
|
||||||
|
EventID: fsevents.LatestEventID(),
|
||||||
},
|
},
|
||||||
sm: &sync.Mutex{},
|
sm: &sync.Mutex{},
|
||||||
events: make(chan FileEvent),
|
events: make(chan FileEvent),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user