Merge remote-tracking branch 'origin/v2.10.6-bug100770-inotify-leak' into release-v2.10-patches
diff --git a/inotify_tracker.go b/inotify_tracker.go
new file mode 100644
index 0000000..6aaa46d
--- /dev/null
+++ b/inotify_tracker.go
@@ -0,0 +1,49 @@
+package tail
+
+import (
+ "github.com/howeyc/fsnotify"
+ "log"
+ "sync"
+)
+
+type InotifyTracker struct {
+ mux sync.Mutex
+ watchers map[*fsnotify.Watcher]bool
+}
+
+func NewInotifyTracker() *InotifyTracker {
+ t := new(InotifyTracker)
+ t.watchers = make(map[*fsnotify.Watcher]bool)
+ return t
+}
+
+func (t *InotifyTracker) NewWatcher() (*fsnotify.Watcher, error) {
+ t.mux.Lock()
+ defer t.mux.Unlock()
+ w, err := fsnotify.NewWatcher()
+ if err == nil {
+ t.watchers[w] = true
+ }
+ return w, err
+}
+
+func (t *InotifyTracker) CloseWatcher(w *fsnotify.Watcher) (err error) {
+ t.mux.Lock()
+ defer t.mux.Unlock()
+ if _, ok := t.watchers[w]; ok {
+ err = w.Close()
+ delete(t.watchers, w)
+ }
+ return
+}
+
+func (t *InotifyTracker) CloseAll() {
+ t.mux.Lock()
+ defer t.mux.Unlock()
+ for w, _ := range t.watchers {
+ if err := w.Close(); err != nil {
+ log.Printf("Error closing watcher: %v", err)
+ }
+ delete(t.watchers, w)
+ }
+}
diff --git a/tail.go b/tail.go
index c4ecd54..1080c8c 100644
--- a/tail.go
+++ b/tail.go
@@ -13,8 +13,8 @@
)
type Line struct {
- Text string
- Time time.Time
+ Text string
+ Time time.Time
}
type Config struct {
@@ -156,7 +156,7 @@
for _, line := range partitionString(string(line), tail.MaxLineSize) {
tail.Lines <- &Line{line, now}
}
- }else{
+ } else {
tail.Lines <- &Line{string(line), now}
}
}
@@ -221,7 +221,7 @@
panic("invalid chunkSize")
}
length := len(s)
- chunks := 1 + length/chunkSize
+ chunks := 1 + length/chunkSize
start := 0
end := chunkSize
parts := make([]string, 0, chunks)
diff --git a/tail_test.go b/tail_test.go
index c6cf53b..6d754d3 100644
--- a/tail_test.go
+++ b/tail_test.go
@@ -102,11 +102,10 @@
// to read all lines.
<-time.After(100 * time.Millisecond)
t.RemoveFile("test.txt")
-
+
tail.Stop()
}
-
// Test library
type TailTest struct {
diff --git a/watch.go b/watch.go
index fbb7568..dc32c21 100644
--- a/watch.go
+++ b/watch.go
@@ -4,12 +4,15 @@
package tail
import (
+ "fmt"
"github.com/howeyc/fsnotify"
"os"
"path/filepath"
"time"
)
+var inotifyTracker *InotifyTracker
+
type FileWatcher interface {
BlockUntilExists() error
ChangeEvents() chan bool
@@ -28,28 +31,26 @@
// BlockUntilExists blocks until the file comes into existence. If the
// file already exists, then block until it is created again.
func (fw *InotifyFileWatcher) BlockUntilExists() error {
- w, err := fsnotify.NewWatcher()
+ w, err := inotifyTracker.NewWatcher()
if err != nil {
return err
}
- defer w.Close()
+ defer inotifyTracker.CloseWatcher(w)
err = w.WatchFlags(filepath.Dir(fw.Filename), fsnotify.FSN_CREATE)
if err != nil {
return err
}
- defer w.RemoveWatch(filepath.Dir(fw.Filename))
- for {
- evt := <-w.Event
+ for evt := range w.Event {
if evt.Name == fw.Filename {
- break
+ return nil
}
}
- return nil
+ return fmt.Errorf("Watcher closed")
}
// ChangeEvents returns a channel that gets updated when the file is ready to be read.
func (fw *InotifyFileWatcher) ChangeEvents() chan bool {
- w, err := fsnotify.NewWatcher()
+ w, err := inotifyTracker.NewWatcher()
if err != nil {
panic(err)
}
@@ -61,16 +62,14 @@
ch := make(chan bool)
go func() {
- for {
- evt := <-w.Event
+ for evt := range w.Event {
switch {
case evt.IsDelete():
fallthrough
case evt.IsRename():
close(ch)
- w.RemoveWatch(fw.Filename)
- w.Close()
+ inotifyTracker.CloseWatcher(w)
return
case evt.IsModify():
@@ -155,3 +154,13 @@
return ch
}
+
+// Cleanup removes open inotify watchers (as the Linux kernel doesn't do it upon
+// process exit).
+func Cleanup() {
+ inotifyTracker.CloseAll()
+}
+
+func init() {
+ inotifyTracker = NewInotifyTracker()
+}