github.com/uriddle/docker@v0.0.0-20210926094723-4072e6aeb013/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  	"gopkg.in/fsnotify.v1"
    13  )
    14  
    15  var (
    16  	// errPollerClosed is returned when the poller is closed
    17  	errPollerClosed = errors.New("poller is closed")
    18  	// errNoSuchPoller is returned when trying to remove a watch that doesn't exist
    19  	errNoSuchWatch = errors.New("poller 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 == true {
    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 == true {
    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  	close(w.events)
   122  	close(w.errors)
   123  	return nil
   124  }
   125  
   126  // sendEvent publishes the specified event to the events channel
   127  func (w *filePoller) sendEvent(e fsnotify.Event, chClose <-chan struct{}) error {
   128  	select {
   129  	case w.events <- e:
   130  	case <-chClose:
   131  		return fmt.Errorf("closed")
   132  	}
   133  	return nil
   134  }
   135  
   136  // sendErr publishes the specified error to the errors channel
   137  func (w *filePoller) sendErr(e error, chClose <-chan struct{}) error {
   138  	select {
   139  	case w.errors <- e:
   140  	case <-chClose:
   141  		return fmt.Errorf("closed")
   142  	}
   143  	return nil
   144  }
   145  
   146  // watch is responsible for polling the specified file for changes
   147  // upon finding changes to a file or errors, sendEvent/sendErr is called
   148  func (w *filePoller) watch(f *os.File, lastFi os.FileInfo, chClose chan struct{}) {
   149  	for {
   150  		time.Sleep(watchWaitTime)
   151  		select {
   152  		case <-chClose:
   153  			logrus.Debugf("watch for %s closed", f.Name())
   154  			return
   155  		default:
   156  		}
   157  
   158  		fi, err := os.Stat(f.Name())
   159  		if err != nil {
   160  			// if we got an error here and lastFi is not set, we can presume that nothing has changed
   161  			// This should be safe since before `watch()` is called, a stat is performed, there is any error `watch` is not called
   162  			if lastFi == nil {
   163  				continue
   164  			}
   165  			// If it doesn't exist at this point, it must have been removed
   166  			// no need to send the error here since this is a valid operation
   167  			if os.IsNotExist(err) {
   168  				if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Remove, Name: f.Name()}, chClose); err != nil {
   169  					return
   170  				}
   171  				lastFi = nil
   172  				continue
   173  			}
   174  			// at this point, send the error
   175  			if err := w.sendErr(err, chClose); err != nil {
   176  				return
   177  			}
   178  			continue
   179  		}
   180  
   181  		if lastFi == nil {
   182  			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Create, Name: fi.Name()}, chClose); err != nil {
   183  				return
   184  			}
   185  			lastFi = fi
   186  			continue
   187  		}
   188  
   189  		if fi.Mode() != lastFi.Mode() {
   190  			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Chmod, Name: fi.Name()}, chClose); err != nil {
   191  				return
   192  			}
   193  			lastFi = fi
   194  			continue
   195  		}
   196  
   197  		if fi.ModTime() != lastFi.ModTime() || fi.Size() != lastFi.Size() {
   198  			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Write, Name: fi.Name()}, chClose); err != nil {
   199  				return
   200  			}
   201  			lastFi = fi
   202  			continue
   203  		}
   204  	}
   205  }