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()
+}