Skip to content

Commit f0f7cfb

Browse files
authored
perf: throttle watch updates (gokcehan#2311)
* perf: throttle watch updates * Fix file not exist error in the UI * Use separate timeout for each file
1 parent c3dbf0a commit f0f7cfb

File tree

2 files changed

+59
-52
lines changed

2 files changed

+59
-52
lines changed

nav.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ func readdir(path string) ([]*file, error) {
175175

176176
files := make([]*file, 0, len(names))
177177
for _, fname := range names {
178-
files = append(files, newFile(filepath.Join(path, fname)))
178+
file := newFile(filepath.Join(path, fname))
179+
if !os.IsNotExist(file.err) {
180+
files = append(files, file)
181+
}
179182
}
180183

181184
return files, err

watch.go

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,27 @@ import (
1010
)
1111

1212
type watch struct {
13-
watcher *fsnotify.Watcher
14-
events <-chan fsnotify.Event
15-
quit chan struct{}
16-
loads map[string]bool
17-
loadTimer *time.Timer
18-
updates map[string]bool
19-
updateTimer *time.Timer
20-
dirChan chan<- *dir
21-
fileChan chan<- *file
22-
delChan chan<- string
13+
watcher *fsnotify.Watcher
14+
events <-chan fsnotify.Event
15+
quit chan struct{}
16+
pending map[watchUpdate]bool
17+
timeout chan watchUpdate
18+
dirChan chan<- *dir
19+
fileChan chan<- *file
20+
delChan chan<- string
2321
}
2422

2523
func newWatch(dirChan chan<- *dir, fileChan chan<- *file, delChan chan<- string) *watch {
26-
return &watch{
27-
quit: make(chan struct{}),
28-
loads: make(map[string]bool),
29-
loadTimer: time.NewTimer(0),
30-
updates: make(map[string]bool),
31-
updateTimer: time.NewTimer(0),
32-
dirChan: dirChan,
33-
fileChan: fileChan,
34-
delChan: delChan,
24+
watch := &watch{
25+
quit: make(chan struct{}),
26+
pending: make(map[watchUpdate]bool),
27+
timeout: make(chan watchUpdate, 1024),
28+
dirChan: dirChan,
29+
fileChan: fileChan,
30+
delChan: delChan,
3531
}
32+
33+
return watch
3634
}
3735

3836
func (watch *watch) start() {
@@ -83,17 +81,17 @@ func (watch *watch) loop() {
8381
case ev := <-watch.events:
8482
if ev.Has(fsnotify.Create) {
8583
for _, path := range watch.getSameDirs(filepath.Dir(ev.Name)) {
86-
watch.addLoad(path)
87-
watch.addUpdate(path)
84+
watch.addUpdate(watchUpdate{"dir", path})
85+
watch.addUpdate(watchUpdate{"file", path})
8886
}
8987
}
9088

9189
if ev.Has(fsnotify.Remove) || ev.Has(fsnotify.Rename) {
9290
dir, file := filepath.Split(ev.Name)
9391
for _, path := range watch.getSameDirs(dir) {
9492
watch.delChan <- filepath.Join(path, file)
95-
watch.addLoad(path)
96-
watch.addUpdate(path)
93+
watch.addUpdate(watchUpdate{"dir", path})
94+
watch.addUpdate(watchUpdate{"file", path})
9795
}
9896
}
9997

@@ -108,47 +106,53 @@ func (watch *watch) loop() {
108106

109107
dir, file := filepath.Split(ev.Name)
110108
for _, path := range watch.getSameDirs(dir) {
111-
watch.addUpdate(filepath.Join(path, file))
109+
watch.addUpdate(watchUpdate{"file", filepath.Join(path, file)})
112110
}
113111
}
114-
case <-watch.loadTimer.C:
115-
for path := range watch.loads {
116-
if _, err := os.Lstat(path); err != nil {
117-
continue
118-
}
119-
dir := newDir(path)
120-
dir.sort()
121-
watch.dirChan <- dir
122-
}
123-
clear(watch.loads)
124-
case <-watch.updateTimer.C:
125-
for path := range watch.updates {
126-
if _, err := os.Lstat(path); err != nil {
127-
continue
128-
}
129-
watch.fileChan <- newFile(path)
112+
case update := <-watch.timeout:
113+
if watch.pending[update] {
114+
watch.processUpdate(update)
115+
time.AfterFunc(100*time.Millisecond, func() { watch.timeout <- update })
116+
watch.pending[update] = false
117+
} else {
118+
delete(watch.pending, update)
130119
}
131-
clear(watch.updates)
132120
case <-watch.quit:
133121
return
134122
}
135123
}
136124
}
137125

138-
func (watch *watch) addLoad(path string) {
139-
if len(watch.loads) == 0 {
140-
watch.loadTimer.Stop()
141-
watch.loadTimer.Reset(10 * time.Millisecond)
126+
type watchUpdate struct {
127+
kind string
128+
path string
129+
}
130+
131+
func (watch *watch) addUpdate(update watchUpdate) {
132+
// process an update immediately if is the first time, otherwise store it
133+
// and process only after a timeout to reduce the number of actual loads
134+
if _, ok := watch.pending[update]; !ok {
135+
watch.processUpdate(update)
136+
time.AfterFunc(100*time.Millisecond, func() { watch.timeout <- update })
137+
watch.pending[update] = false
138+
} else {
139+
watch.pending[update] = true
142140
}
143-
watch.loads[path] = true
144141
}
145142

146-
func (watch *watch) addUpdate(path string) {
147-
if len(watch.updates) == 0 {
148-
watch.updateTimer.Stop()
149-
watch.updateTimer.Reset(10 * time.Millisecond)
143+
func (watch *watch) processUpdate(update watchUpdate) {
144+
switch update.kind {
145+
case "dir":
146+
if _, err := os.Lstat(update.path); err == nil {
147+
dir := newDir(update.path)
148+
dir.sort()
149+
watch.dirChan <- dir
150+
}
151+
case "file":
152+
if _, err := os.Lstat(update.path); err == nil {
153+
watch.fileChan <- newFile(update.path)
154+
}
150155
}
151-
watch.updates[path] = true
152156
}
153157

154158
// Hacky workaround since fsnotify reports changes for only one path if a

0 commit comments

Comments
 (0)