Skip to content

Commit

Permalink
use inode to detect if the file is recreated/replaced and only listen…
Browse files Browse the repository at this point in the history
… to create events.
  • Loading branch information
dhiaayachi committed Feb 10, 2022
1 parent 74acc47 commit 2f6d9f6
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 23 deletions.
39 changes: 25 additions & 14 deletions agent/config/watcher.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package config

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/hashicorp/go-hclog"
"os"
"syscall"
"time"
)

Expand All @@ -21,7 +21,7 @@ type Watcher struct {
}

type watchedFile struct {
hash string
iNode uint64
isEventWatched bool
}

Expand All @@ -43,11 +43,11 @@ func (w Watcher) Add(filename string) error {
if err != nil {
return err
}
hash, err := w.hashFile(filename)
hash, err := w.getInode(filename)
if err != nil {
return err
}
w.configFiles[filename] = &watchedFile{hash: hash, isEventWatched: true}
w.configFiles[filename] = &watchedFile{iNode: hash, isEventWatched: true}
return nil
}

Expand All @@ -69,15 +69,24 @@ func (w Watcher) Close() error {
return nil
}

func (w Watcher) hashFile(filename string) (string, error) {
file, err := os.ReadFile(filename)
func (w Watcher) getInode(filename string) (uint64, error) {
realFilename := filename
linkedFile, err := os.Readlink(filename)
if err == nil {
realFilename = linkedFile
}
fileinfo, err := os.Stat(realFilename)

if err != nil {
return "", err
return 0, err
}
stat, ok := fileinfo.Sys().(*syscall.Stat_t)
if !ok {
return 0, fmt.Errorf("Not a syscall.Stat_t %v", fileinfo.Sys())
}
hasher := sha256.New()

hash := hex.EncodeToString(hasher.Sum(file))
return hash, nil
w.logger.Info("read inode ", "inode", stat.Ino)
return stat.Ino, nil
}

func (w Watcher) watch() {
Expand Down Expand Up @@ -114,7 +123,9 @@ func (w Watcher) watch() {
}

func (w Watcher) handleEvent(event fsnotify.Event) error {
if !isWrite(event) && !isRemove(event) && !isCreate(event) {

// we only want Create event to avoid triggering a relaod on file modification
if !isCreate(event) {
return nil
}
// If the file was removed, set it to be readded to watch when created
Expand All @@ -126,7 +137,7 @@ func (w Watcher) handleEvent(event fsnotify.Event) error {

func (w Watcher) reconcile() {
for filename, configFile := range w.configFiles {
newHash, err := w.hashFile(filename)
newInode, err := w.getInode(filename)
if err != nil {
continue
}
Expand All @@ -137,7 +148,7 @@ func (w Watcher) reconcile() {
configFile.isEventWatched = true
}
}
if configFile.hash != newHash {
if configFile.iNode != newInode {
w.handleFunc(&WatcherEvent{Filename: filename})
}
}
Expand Down
26 changes: 17 additions & 9 deletions agent/config/watcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestWatcherAddRemoveExist(t *testing.T) {
require.NoError(t, err)
h, ok := w.configFiles[file.Name()]
require.True(t, ok)
require.Equal(t, "7465737420636f6e666967e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", h.hash)
require.NotEqual(t, 0, h.iNode)
err = w.Remove(file.Name())
require.NoError(t, err)
_, ok = w.configFiles[file.Name()]
Expand Down Expand Up @@ -83,7 +83,7 @@ func TestEventWatcherWrite(t *testing.T) {
require.NoError(t, err)
err = file.Sync()
require.NoError(t, err)
require.NoError(t, assertEvent(file.Name(), watcherCh))
require.Error(t, assertEvent(file.Name(), watcherCh), "timedout waiting for event")
}

func TestEventWatcherRead(t *testing.T) {
Expand Down Expand Up @@ -196,13 +196,14 @@ func TestEventWatcherMove(t *testing.T) {
err = file2.Close()
require.NoError(t, err)

err = w.Add(file2.Name())
err = w.Add(file.Name())

require.NoError(t, err)
w.reconcileTimeout = 20 * time.Millisecond
w.Start()
os.Rename(file.Name(), file2.Name())
require.NoError(t, assertEvent(file2.Name(), watcherCh))
err = os.Rename(file2.Name(), file.Name())
require.NoError(t, err)
require.NoError(t, assertEvent(file.Name(), watcherCh))
}

func TestEventReconcile(t *testing.T) {
Expand All @@ -221,15 +222,22 @@ func TestEventReconcile(t *testing.T) {
err = file.Sync()
require.NoError(t, err)

file2 := testutil.TempFile(t, "temp_config"+randomString(12))
_, err = file2.WriteString("test config 2")
require.NoError(t, err)
err = file2.Sync()
require.NoError(t, err)
err = file2.Close()
require.NoError(t, err)

err = w.Add(file.Name())
require.NoError(t, err)
w.reconcileTimeout = 50 * time.Millisecond
// remove the file from the internal watcher to only trigger the reconcile
w.watcher.Remove(file.Name())
w.Start()
_, err = file.WriteString("test config 2")
err = w.watcher.Remove(file.Name())
require.NoError(t, err)
err = file.Sync()
w.Start()
err = os.Rename(file2.Name(), file.Name())
require.NoError(t, err)
require.NoError(t, assertEvent(file.Name(), watcherCh))
}
Expand Down

0 comments on commit 2f6d9f6

Please sign in to comment.