Skip to content

Commit

Permalink
Avoid firing event if no files changed
Browse files Browse the repository at this point in the history
If the changes that triggered the event were only to known files, and
all of those files have the same hashes as when we began, we shouldn't
actually reload the command.

This optimization does *not* short circuit command reload if any of
these conditions hold:

- a changed file is larger than 20MB, e.g. changes to these files
always trigger a reload.

- more than 500 files changed

- a directory or an unknown file was changed.

Fixes jmhodges#23.
  • Loading branch information
kevinburke committed Jan 15, 2018
1 parent 0ebecc6 commit 969ac62
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 7 deletions.
36 changes: 35 additions & 1 deletion justrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
Expand Down Expand Up @@ -77,14 +78,16 @@ func main() {
go waitForInterrupt(sigCh, cmd)

cmdCh := make(chan event, 100)
_, err := watch(inputPaths, ignoreFlag, cmdCh)
userPaths, err := watch(inputPaths, ignoreFlag, cmdCh)
if err != nil {
log.Fatal(err)
}

wasDelayed := false

lastStartTime := time.Now()
changedFiles := make([]string, 0)
mustChange := false
cmd.Reload()
tick := time.NewTicker(*delayDur)
for {
Expand All @@ -93,6 +96,11 @@ func main() {
if !ok {
return
}
if h, ok := userPaths[ev.Event.Name]; !ok || h == nil {
mustChange = true
} else {
changedFiles = append(changedFiles, ev.Event.Name)
}
if lastStartTime.After(ev.Time) {
continue
}
Expand All @@ -104,6 +112,8 @@ func main() {
continue
}
wasDelayed = false
mustChange = false
changedFiles = changedFiles[:0]
lastStartTime = time.Now()
cmd.Reload()
tick.Stop()
Expand All @@ -112,6 +122,30 @@ func main() {
if wasDelayed {
wasDelayed = false
lastStartTime = time.Now()
if mustChange == false && len(changedFiles) < 512 {
allEqual := true
// check hashes.
for i := range changedFiles {
d, err := digest(changedFiles[i])
if err != nil {
allEqual = false
break
}
if !bytes.Equal(d, userPaths[changedFiles[i]]) {
// store the new digest, keep searching so we get
// new digest for all files.
userPaths[changedFiles[i]] = d
allEqual = false
}
}
mustChange = false
l := len(changedFiles)
changedFiles = changedFiles[:0]
if allEqual {
log.Printf("%d files changed but all have same contents, continuing", l)
continue
}
}
cmd.Reload()
}
}
Expand Down
47 changes: 41 additions & 6 deletions watch.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
package main

import (
"bufio"
"crypto/sha256"
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"time"

"github.com/fsnotify/fsnotify"
)

const maxHashedFileSize = 20 * 1024 * 1024

func digest(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return nil, err
}
if stat.IsDir() {
return nil, nil
}
h := sha256.New()
// TODO large file check.
br := bufio.NewReader(f)
if _, err := io.Copy(h, io.LimitReader(br, maxHashedFileSize)); err != nil {
return nil, err
}
if b, _ := br.Peek(1); len(b) > 0 {
// too big
return nil, nil
}
return h.Sum(nil), nil
}

// watch watches the input paths. The returned Watcher should only be used in
// tests.
func watch(inputPaths, ignoredPaths []string, cmdCh chan<- event) (*fsnotify.Watcher, error) {
func watch(inputPaths, ignoredPaths []string, cmdCh chan<- event) (map[string][]byte, error) {

This comment has been minimized.

Copy link
@kevinburke

kevinburke Jan 15, 2018

Author Owner

Weren't ever using the first value here.

// Creates an Ignorer that just ignores file paths the user
// specifically asked to be ignored.
ui, err := createUserIgnorer(ignoredPaths)
Expand All @@ -29,23 +61,25 @@ func watch(inputPaths, ignoredPaths []string, cmdCh chan<- event) (*fsnotify.Wat
// Watch user-specified paths and create a set of them for walking
// later. Paths that are both asked to be watched and ignored by
// the user are ignored.
userPaths := make(map[string]bool)
userPaths := make(map[string][]byte)
includedHiddenFiles := make(map[string]bool)
for _, path := range inputPaths {
fullPath, err := filepath.Abs(path)
if err != nil {
w.Close()
return nil, errors.New("unable to get current working directory while working with user-watched paths")
}
if userPaths[fullPath] || ui.IsIgnored(path) {
_, found := userPaths[fullPath]
if found || ui.IsIgnored(path) {
continue
}
err = w.Add(fullPath)
if err != nil {
w.Close()
return nil, fmt.Errorf("unable to watch '%s': %s", path, err)
}
userPaths[fullPath] = true
d, _ := digest(fullPath)
userPaths[fullPath] = d
}

// Create some useful sets from the user-specified paths to be
Expand Down Expand Up @@ -73,7 +107,8 @@ func watch(inputPaths, ignoredPaths []string, cmdCh chan<- event) (*fsnotify.Wat
}

dirPath := filepath.Dir(fullPath)
if !userPaths[dirPath] && dirPath != "" {
_, foundDir := userPaths[dirPath]
if !foundDir && dirPath != "" {
if !renameDirs[dirPath] {
err = w.Add(dirPath)
if err != nil {
Expand All @@ -93,7 +128,7 @@ func watch(inputPaths, ignoredPaths []string, cmdCh chan<- event) (*fsnotify.Wat
}

go listenForEvents(w, cmdCh, ig)
return w, nil
return userPaths, nil
}

type event struct {
Expand Down

0 comments on commit 969ac62

Please sign in to comment.