Remove filewatcher package

Since #339 was reverted, the filewatcher functionality is no longer used.

Signed-off-by: Jan Dubois <jan.dubois@suse.com>
This commit is contained in:
Jan Dubois
2025-02-03 12:25:01 -08:00
parent f0f18025e5
commit 83d33e0aa3
2 changed files with 0 additions and 164 deletions

View File

@@ -1,63 +0,0 @@
package utils
import (
"os"
"time"
)
// FileWatcher is an utility that
type FileWatcher struct {
path string
closeCh chan struct{}
pollInterval time.Duration
}
func NewFileWatcher(path string) *FileWatcher {
return &FileWatcher{
path: path,
pollInterval: 5 * time.Second, // 5s is the default inode cache timeout in linux for most systems.
closeCh: make(chan struct{}),
}
}
func (fw *FileWatcher) Start(changeHandler func()) {
prevModTime := fw.fileModTime(fw.path)
// use polling-based approach to detect file changes
// we can't use fsnotify/fsnotify due to issues with symlink+socket. see #462.
go func() {
for {
select {
case _, ok := <-fw.closeCh:
if !ok {
return // watcher is closed.
}
case <-time.After(fw.pollInterval):
}
modTime := fw.fileModTime(fw.path)
if modTime.IsZero() {
continue // file does not exists
}
if !prevModTime.Equal(modTime) {
changeHandler()
prevModTime = modTime
}
}
}()
}
func (fw *FileWatcher) fileModTime(path string) time.Time {
info, err := os.Stat(path)
if err != nil {
return time.Time{}
}
return info.ModTime()
}
func (fw *FileWatcher) Stop() {
close(fw.closeCh)
}

View File

@@ -1,101 +0,0 @@
package utils
import (
"os"
"path/filepath"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestFileWatcher(t *testing.T) {
if testing.Short() {
// we can't really speed up the test as we need to wait for the
// inode cache to expire so that we can read the latest
// file's modtime.
t.Skip("skipping test in short mode.")
}
t.Parallel()
assertSpec := func(t *testing.T, watchedPath, filePath string) {
t.Helper()
fw := NewFileWatcher(watchedPath)
var numTriggered atomic.Int64
assertNumTriggered := func(expected int) {
time.Sleep(fw.pollInterval + 200*time.Millisecond)
assert.Equal(t, int64(expected), numTriggered.Load())
}
fw.Start(func() {
numTriggered.Add(1)
})
// important: adding a random sleep here so that the next fw's poll
// results in inode cache miss, hence calling stat will return the latest
// modtime.
time.Sleep(1 * time.Second)
// CASE: can detect changes to the file.
if err := os.WriteFile(filePath, []byte("2"), 0o600); err != nil {
panic(err)
}
assertNumTriggered(1)
// CASE: can detect "swap"-based file modification.
tmpFile := filepath.Join(filepath.Dir(filePath), "tmp.txt")
if err := os.WriteFile(tmpFile, []byte("lol"), 0o600); err != nil {
panic(err)
}
if err := os.Rename(tmpFile, filePath); err != nil {
panic(err)
}
assertNumTriggered(2)
// CASE: combine multiple partial writes into single event.
fd, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0o600)
if err != nil {
panic(err)
}
// we assume these writes happens in less than 50ms.
_, _ = fd.Write([]byte("a"))
_ = fd.Sync()
_, _ = fd.Write([]byte("b"))
fd.Close()
assertNumTriggered(3)
// CASE: closed file watcher should not call the callback after Stop() is called.
fw.Stop()
if err := os.WriteFile(filePath, []byte("2"), 0o600); err != nil {
panic(err)
}
assertNumTriggered(3) // does not change.
}
t.Run("normal file", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
path := filepath.Join(dir, "file.txt")
_ = os.WriteFile(path, []byte("1"), 0o600)
assertSpec(t, path, path)
})
t.Run("symlink", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
path := filepath.Join(dir, "file.txt")
_ = os.WriteFile(path, []byte("1"), 0o600)
symlinkPath := filepath.Join(dir, "symlink.txt")
_ = os.Symlink(path, symlinkPath)
assertSpec(t, symlinkPath, path)
})
}