github.com/ttys3/engine@v17.12.1-ce-rc2+incompatible/pkg/filenotify/poller.go (about)

     1  package filenotify
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/sirupsen/logrus"
    11  
    12  	"github.com/fsnotify/fsnotify"
    13  )
    14  
    15  var (
    16  	// errPollerClosed is returned when the poller is closed
    17  	errPollerClosed = errors.New("poller is closed")
    18  	// errNoSuchWatch is returned when trying to remove a watch that doesn't exist
    19  	errNoSuchWatch = errors.New("watch does not exist")
    20  )
    21  
    22  // watchWaitTime is the time to wait between file poll loops
    23  const watchWaitTime = 200 * time.Millisecond
    24  
    25  // filePoller is used to poll files for changes, especially in cases where fsnotify
    26  // can't be run (e.g. when inotify handles are exhausted)
    27  // filePoller satisfies the FileWatcher interface
    28  type filePoller struct {
    29  	// watches is the list of files currently being polled, close the associated channel to stop the watch
    30  	watches map[string]chan struct{}
    31  	// events is the channel to listen to for watch events
    32  	events chan fsnotify.Event
    33  	// errors is the channel to listen to for watch errors
    34  	errors chan error
    35  	// mu locks the poller for modification
    36  	mu sync.Mutex
    37  	// closed is used to specify when the poller has already closed
    38  	closed bool
    39  }
    40  
    41  // Add adds a filename to the list of watches
    42  // once added the file is polled for changes in a separate goroutine
    43  func (w *filePoller) Add(name string) error {
    44  	w.mu.Lock()
    45  	defer w.mu.Unlock()
    46  
    47  	if w.closed {
    48  		return errPollerClosed
    49  	}
    50  
    51  	f, err := os.Open(name)
    52  	if err != nil {
    53  		return err
    54  	}
    55  	fi, err := os.Stat(name)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	if w.watches == nil {
    61  		w.watches = make(map[string]chan struct{})
    62  	}
    63  	if _, exists := w.watches[name]; exists {
    64  		return fmt.Errorf("watch exists")
    65  	}
    66  	chClose := make(chan struct{})
    67  	w.watches[name] = chClose
    68  
    69  	go w.watch(f, fi, chClose)
    70  	return nil
    71  }
    72  
    73  // Remove stops and removes watch with the specified name
    74  func (w *filePoller) Remove(name string) error {
    75  	w.mu.Lock()
    76  	defer w.mu.Unlock()
    77  	return w.remove(name)
    78  }
    79  
    80  func (w *filePoller) remove(name string) error {
    81  	if w.closed {
    82  		return errPollerClosed
    83  	}
    84  
    85  	chClose, exists := w.watches[name]
    86  	if !exists {
    87  		return errNoSuchWatch
    88  	}
    89  	close(chClose)
    90  	delete(w.watches, name)
    91  	return nil
    92  }
    93  
    94  // Events returns the event channel
    95  // This is used for notifications on events about watched files
    96  func (w *filePoller) Events() <-chan fsnotify.Event {
    97  	return w.events
    98  }
    99  
   100  // Errors returns the errors channel
   101  // This is used for notifications about errors on watched files
   102  func (w *filePoller) Errors() <-chan error {
   103  	return w.errors
   104  }
   105  
   106  // Close closes the poller
   107  // All watches are stopped, removed, and the poller cannot be added to
   108  func (w *filePoller) Close() error {
   109  	w.mu.Lock()
   110  	defer w.mu.Unlock()
   111  
   112  	if w.closed {
   113  		return nil
   114  	}
   115  
   116  	w.closed = true
   117  	for name := range w.watches {
   118  		w.remove(name)
   119  		delete(w.watches, name)
   120  	}
   121  	return nil
   122  }
   123  
   124  // sendEvent publishes the specified event to the events channel
   125  func (w *filePoller) sendEvent(e fsnotify.Event, chClose <-chan struct{}) error {
   126  	select {
   127  	case w.events <- e:
   128  	case <-chClose:
   129  		return fmt.Errorf("closed")
   130  	}
   131  	return nil
   132  }
   133  
   134  // sendErr publishes the specified error to the errors channel
   135  func (w *filePoller) sendErr(e error, chClose <-chan struct{}) error {
   136  	select {
   137  	case w.errors <- e:
   138  	case <-chClose:
   139  		return fmt.Errorf("closed")
   140  	}
   141  	return nil
   142  }
   143  
   144  // watch is responsible for polling the specified file for changes
   145  // upon finding changes to a file or errors, sendEvent/sendErr is called
   146  func (w *filePoller) watch(f *os.File, lastFi os.FileInfo, chClose chan struct{}) {
   147  	defer f.Close()
   148  	for {
   149  		time.Sleep(watchWaitTime)
   150  		select {
   151  		case <-chClose:
   152  			logrus.Debugf("watch for %s closed", f.Name())
   153  			return
   154  		default:
   155  		}
   156  
   157  		fi, err := os.Stat(f.Name())
   158  		if err != nil {
   159  			// if we got an error here and lastFi is not set, we can presume that nothing has changed
   160  			// This should be safe since before `watch()` is called, a stat is performed, there is any error `watch` is not called
   161  			if lastFi == nil {
   162  				continue
   163  			}
   164  			// If it doesn't exist at this point, it must have been removed
   165  			// no need to send the error here since this is a valid operation
   166  			if os.IsNotExist(err) {
   167  				if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Remove, Name: f.Name()}, chClose); err != nil {
   168  					return
   169  				}
   170  				lastFi = nil
   171  				continue
   172  			}
   173  			// at this point, send the error
   174  			if err := w.sendErr(err, chClose); err != nil {
   175  				return
   176  			}
   177  			continue
   178  		}
   179  
   180  		if lastFi == nil {
   181  			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Create, Name: fi.Name()}, chClose); err != nil {
   182  				return
   183  			}
   184  			lastFi = fi
   185  			continue
   186  		}
   187  
   188  		if fi.Mode() != lastFi.Mode() {
   189  			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Chmod, Name: fi.Name()}, chClose); err != nil {
   190  				return
   191  			}
   192  			lastFi = fi
   193  			continue
   194  		}
   195  
   196  		if fi.ModTime() != lastFi.ModTime() || fi.Size() != lastFi.Size() {
   197  			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Write, Name: fi.Name()}, chClose); err != nil {
   198  				return
   199  			}
   200  			lastFi = fi
   201  			continue
   202  		}
   203  	}
   204  }