github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/pkg/filenotify/poller.go (about)

     1  package filenotify // import "github.com/docker/docker/pkg/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  		f.Close()
    58  		return err
    59  	}
    60  
    61  	if w.watches == nil {
    62  		w.watches = make(map[string]chan struct{})
    63  	}
    64  	if _, exists := w.watches[name]; exists {
    65  		f.Close()
    66  		return fmt.Errorf("watch exists")
    67  	}
    68  	chClose := make(chan struct{})
    69  	w.watches[name] = chClose
    70  
    71  	go w.watch(f, fi, chClose)
    72  	return nil
    73  }
    74  
    75  // Remove stops and removes watch with the specified name
    76  func (w *filePoller) Remove(name string) error {
    77  	w.mu.Lock()
    78  	defer w.mu.Unlock()
    79  	return w.remove(name)
    80  }
    81  
    82  func (w *filePoller) remove(name string) error {
    83  	if w.closed {
    84  		return errPollerClosed
    85  	}
    86  
    87  	chClose, exists := w.watches[name]
    88  	if !exists {
    89  		return errNoSuchWatch
    90  	}
    91  	close(chClose)
    92  	delete(w.watches, name)
    93  	return nil
    94  }
    95  
    96  // Events returns the event channel
    97  // This is used for notifications on events about watched files
    98  func (w *filePoller) Events() <-chan fsnotify.Event {
    99  	return w.events
   100  }
   101  
   102  // Errors returns the errors channel
   103  // This is used for notifications about errors on watched files
   104  func (w *filePoller) Errors() <-chan error {
   105  	return w.errors
   106  }
   107  
   108  // Close closes the poller
   109  // All watches are stopped, removed, and the poller cannot be added to
   110  func (w *filePoller) Close() error {
   111  	w.mu.Lock()
   112  	defer w.mu.Unlock()
   113  
   114  	if w.closed {
   115  		return nil
   116  	}
   117  
   118  	for name := range w.watches {
   119  		w.remove(name)
   120  	}
   121  	w.closed = true
   122  	return nil
   123  }
   124  
   125  // sendEvent publishes the specified event to the events channel
   126  func (w *filePoller) sendEvent(e fsnotify.Event, chClose <-chan struct{}) error {
   127  	select {
   128  	case w.events <- e:
   129  	case <-chClose:
   130  		return fmt.Errorf("closed")
   131  	}
   132  	return nil
   133  }
   134  
   135  // sendErr publishes the specified error to the errors channel
   136  func (w *filePoller) sendErr(e error, chClose <-chan struct{}) error {
   137  	select {
   138  	case w.errors <- e:
   139  	case <-chClose:
   140  		return fmt.Errorf("closed")
   141  	}
   142  	return nil
   143  }
   144  
   145  // watch is responsible for polling the specified file for changes
   146  // upon finding changes to a file or errors, sendEvent/sendErr is called
   147  func (w *filePoller) watch(f *os.File, lastFi os.FileInfo, chClose chan struct{}) {
   148  	defer f.Close()
   149  
   150  	timer := time.NewTimer(watchWaitTime)
   151  	if !timer.Stop() {
   152  		<-timer.C
   153  	}
   154  	defer timer.Stop()
   155  
   156  	for {
   157  		timer.Reset(watchWaitTime)
   158  
   159  		select {
   160  		case <-timer.C:
   161  		case <-chClose:
   162  			logrus.Debugf("watch for %s closed", f.Name())
   163  			return
   164  		}
   165  
   166  		fi, err := os.Stat(f.Name())
   167  		if err != nil {
   168  			// if we got an error here and lastFi is not set, we can presume that nothing has changed
   169  			// This should be safe since before `watch()` is called, a stat is performed, there is any error `watch` is not called
   170  			if lastFi == nil {
   171  				continue
   172  			}
   173  			// If it doesn't exist at this point, it must have been removed
   174  			// no need to send the error here since this is a valid operation
   175  			if os.IsNotExist(err) {
   176  				if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Remove, Name: f.Name()}, chClose); err != nil {
   177  					return
   178  				}
   179  				lastFi = nil
   180  				continue
   181  			}
   182  			// at this point, send the error
   183  			if err := w.sendErr(err, chClose); err != nil {
   184  				return
   185  			}
   186  			continue
   187  		}
   188  
   189  		if lastFi == nil {
   190  			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Create, Name: fi.Name()}, chClose); err != nil {
   191  				return
   192  			}
   193  			lastFi = fi
   194  			continue
   195  		}
   196  
   197  		if fi.Mode() != lastFi.Mode() {
   198  			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Chmod, Name: fi.Name()}, chClose); err != nil {
   199  				return
   200  			}
   201  			lastFi = fi
   202  			continue
   203  		}
   204  
   205  		if fi.ModTime() != lastFi.ModTime() || fi.Size() != lastFi.Size() {
   206  			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Write, Name: fi.Name()}, chClose); err != nil {
   207  				return
   208  			}
   209  			lastFi = fi
   210  			continue
   211  		}
   212  	}
   213  }