Skip to content

Commit

Permalink
Replaced use of raw epoll with netpoller read
Browse files Browse the repository at this point in the history
  • Loading branch information
horahoradev committed Mar 6, 2022
1 parent 712fe1d commit c016463
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 483 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/test.yml
Expand Up @@ -15,6 +15,9 @@ jobs:
- '1.18.0-beta1'
- '1.17'
- '1.16'
multicore:
- true
- false
runs-on: ${{ matrix.os }}
steps:
- name: setup Go
Expand All @@ -29,6 +32,14 @@ jobs:
- name: test
run: |
go test --race ./...
if: matrix.multicore

- name: test
env:
GOMAXPROCS: 1
run: |
go test --race ./...
if: ${{ !matrix.multicore }}

testFreeBSD:
runs-on: macos-10.15
Expand Down
98 changes: 36 additions & 62 deletions inotify.go
Expand Up @@ -22,15 +22,15 @@ import (

// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
mu sync.Mutex // Map access
fd int
poller *fdPoller
watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor)
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
doneResp chan struct{} // Channel to respond to Close
fd int // https://github.com/golang/go/issues/26439 can't call .Fd() on os.FIle or Read will no longer return on Close()
Events chan Event
Errors chan error
mu sync.Mutex // Map access
inotifyFile *os.File
watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor)
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
doneResp chan struct{} // Channel to respond to Close
}

// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
Expand All @@ -40,21 +40,23 @@ func NewWatcher() (*Watcher, error) {
if fd == -1 {
return nil, errno
}
// Create epoll
poller, err := newFdPoller(fd)
if err != nil {
unix.Close(fd)
return nil, err

// Need to set the FD to nonblocking mode in order for SetDeadline methods to work
// Otherwise, blocking i/o operations won't terminate on close
errno = unix.SetNonblock(fd, true)
if errno != nil {
return nil, errno
}

w := &Watcher{
fd: fd,
poller: poller,
watches: make(map[string]*watch),
paths: make(map[int]string),
Events: make(chan Event),
Errors: make(chan error),
done: make(chan struct{}),
doneResp: make(chan struct{}),
fd: fd,
inotifyFile: os.NewFile(uintptr(fd), ""),
watches: make(map[string]*watch),
paths: make(map[int]string),
Events: make(chan Event),
Errors: make(chan error),
done: make(chan struct{}),
doneResp: make(chan struct{}),
}

go w.readEvents()
Expand All @@ -79,8 +81,11 @@ func (w *Watcher) Close() error {
// Send 'close' signal to goroutine, and set the Watcher to closed.
close(w.done)

// Wake up goroutine
w.poller.wake()
// Causes any blocking reads to return with an error, provided the file still supports deadline operations
err := w.inotifyFile.Close()
if err != nil {
return err
}

// Wait for goroutine to close
<-w.doneResp
Expand Down Expand Up @@ -173,50 +178,32 @@ type watch struct {
func (w *Watcher) readEvents() {
var (
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
n int // Number of bytes read with read()
errno error // Syscall errno
ok bool // For poller.wait
)

defer close(w.doneResp)
defer close(w.Errors)
defer close(w.Events)
defer unix.Close(w.fd)
defer w.poller.close()

for {
// See if we have been closed.
if w.isClosed() {
return
}

ok, errno = w.poller.wait()
if errno != nil {
n, err := w.inotifyFile.Read(buf[:])
switch {
case errors.Unwrap(err) == os.ErrClosed:
return
case err != nil:
select {
case w.Errors <- errno:
case w.Errors <- err:
case <-w.done:
return
}
continue
}

if !ok {
continue
}

n, errno = unix.Read(w.fd, buf[:])
// If a signal interrupted execution, see if we've been asked to close, and try again.
// http://man7.org/linux/man-pages/man7/signal.7.html :
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
if errno == unix.EINTR {
continue
}

// unix.Read might have been woken up by Close. If so, we're done.
if w.isClosed() {
return
}

if n < unix.SizeofInotifyEvent {
var err error
if n == 0 {
Expand Down Expand Up @@ -300,20 +287,7 @@ func (w *Watcher) readEvents() {
// against files that do not exist.
func (e *Event) ignoreLinux(mask uint32) bool {
// Ignore anything the inotify API says to ignore
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
return true
}

// If the event is not a DELETE or RENAME, the file must exist.
// Otherwise the event is ignored.
// *Note*: this was put in place because it was seen that a MODIFY
// event was sent after the DELETE. This ignores that MODIFY and
// assumes a DELETE will come or has come if the file doesn't exist.
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
_, statErr := os.Lstat(e.Name)
return os.IsNotExist(statErr)
}
return false
return mask&unix.IN_IGNORED == unix.IN_IGNORED
}

// newEvent returns an platform-independent Event based on an inotify mask.
Expand Down
187 changes: 0 additions & 187 deletions inotify_poller.go

This file was deleted.

0 comments on commit c016463

Please sign in to comment.