1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-07-01 19:24:14 +08:00

godeps: update fsnotify to v1.2.0

This commit is contained in:
Henry
2015-02-27 13:29:25 +01:00
parent df8d474fea
commit cffef5b544
15 changed files with 922 additions and 85 deletions

4
Godeps/Godeps.json generated
View File

@ -237,8 +237,8 @@
}, },
{ {
"ImportPath": "gopkg.in/fsnotify.v1", "ImportPath": "gopkg.in/fsnotify.v1",
"Comment": "v1.1.0", "Comment": "v1.2.0",
"Rev": "f582d920d11386e8ae15227bb5933a8f9b4c3dec" "Rev": "96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0"
}, },
{ {
"ImportPath": "gopkg.in/natefinch/lumberjack.v2", "ImportPath": "gopkg.in/natefinch/lumberjack.v2",

View File

@ -1,10 +1,12 @@
sudo: false
language: go language: go
go: go:
- 1.2 - 1.4.1
- tip
before_script:
- FIXED=$(go fmt ./... | wc -l); if [ $FIXED -gt 0 ]; then echo "gofmt - $FIXED file(s) not formatted correctly, please run gofmt to fix this." && exit 1; fi
# not yet https://github.com/travis-ci/travis-ci/issues/2318
os: os:
- linux - linux
- osx - osx

View File

@ -21,6 +21,7 @@ Kelvin Fo <vmirage@gmail.com>
Matt Layher <mdlayher@gmail.com> Matt Layher <mdlayher@gmail.com>
Nathan Youngman <git@nathany.com> Nathan Youngman <git@nathany.com>
Paul Hammond <paul@paulhammond.org> Paul Hammond <paul@paulhammond.org>
Pieter Droogendijk <pieter@binky.org.uk>
Pursuit92 <JoshChase@techpursuit.net> Pursuit92 <JoshChase@techpursuit.net>
Rob Figueiredo <robfig@gmail.com> Rob Figueiredo <robfig@gmail.com>
Soge Zhang <zhssoge@gmail.com> Soge Zhang <zhssoge@gmail.com>

View File

@ -1,5 +1,15 @@
# Changelog # Changelog
## v1.2.0 / 2015-02-08
* inotify: use epoll to wake up readEvents [#66](https://github.com/go-fsnotify/fsnotify/pull/66) (thanks @PieterD)
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/go-fsnotify/fsnotify/pull/63) (thanks @PieterD)
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/go-fsnotify/fsnotify/issues/59)
## v1.1.1 / 2015-02-05
* inotify: Retry read on EINTR [#61](https://github.com/go-fsnotify/fsnotify/issues/61) (thanks @PieterD)
## v1.1.0 / 2014-12-12 ## v1.1.0 / 2014-12-12
* kqueue: rework internals [#43](https://github.com/go-fsnotify/fsnotify/pull/43) * kqueue: rework internals [#43](https://github.com/go-fsnotify/fsnotify/pull/43)
@ -10,7 +20,7 @@
* remove calls to os.NewSyscallError * remove calls to os.NewSyscallError
* More efficient string concatenation for Event.String() [#52](https://github.com/go-fsnotify/fsnotify/pull/52) (thanks @mdlayher) * More efficient string concatenation for Event.String() [#52](https://github.com/go-fsnotify/fsnotify/pull/52) (thanks @mdlayher)
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/go-fsnotify/fsnotify/issues/48) * kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/go-fsnotify/fsnotify/issues/48)
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/48) * kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51)
## v1.0.4 / 2014-09-07 ## v1.0.4 / 2014-09-07
@ -81,6 +91,10 @@
* no tests for the current implementation * no tests for the current implementation
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
## v0.9.3 / 2014-12-31
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51)
## v0.9.2 / 2014-08-17 ## v0.9.2 / 2014-08-17
* [Backport] Fix missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso) * [Backport] Fix missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso)

View File

@ -1,21 +1,34 @@
# Contributing # Contributing
* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). ## Issues
### Issues
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues). * Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues).
* Please indicate the platform you are running on. * Please indicate the platform you are using fsnotify on.
* A code example to reproduce the problem is appreciated.
### Pull Requests ## Pull Requests
### Contributor License Agreement
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/go-fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/go-fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
Please indicate that you have signed the CLA in your pull request. Please indicate that you have signed the CLA in your pull request.
To hack on fsnotify, please use [the workflow outlined by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/), in short: ### How fsnotify is Developed
1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`) * Development is done on feature branches.
* Tests are run on BSD, Linux, OS X and Windows.
* Pull requests are reviewed and [applied to master][am] using [hub][].
* Maintainers may modify or squash commits rather than asking contributors to.
* To issue a new release, the maintainers will:
* Update the CHANGELOG
* Tag a version, which will become available through gopkg.in.
### How to Fork
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
1. Install from GitHub (`go get -u github.com/go-fsnotify/fsnotify`)
2. Create your feature branch (`git checkout -b my-new-feature`) 2. Create your feature branch (`git checkout -b my-new-feature`)
3. Ensure everything works and the tests pass (see below) 3. Ensure everything works and the tests pass (see below)
4. Commit your changes (`git commit -am 'Add some feature'`) 4. Commit your changes (`git commit -am 'Add some feature'`)
@ -27,15 +40,7 @@ Contribute upstream:
3. Push to the branch (`git push fork my-new-feature`) 3. Push to the branch (`git push fork my-new-feature`)
4. Create a new Pull Request on GitHub 4. Create a new Pull Request on GitHub
If other team members need your patch before it is merged: This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/).
1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`)
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
3. Pull your revisions (`git fetch fork; git checkout -b my-new-feature fork/my-new-feature`)
Notice: For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
Note: The maintainers will update the CHANGELOG on your behalf. Please don't modify it in your pull request.
### Testing ### Testing
@ -62,11 +67,11 @@ Help maintaining fsnotify is welcome. To be a maintainer:
* Submit a pull request and sign the CLA as above. * Submit a pull request and sign the CLA as above.
* You must be able to run the test suite on Mac, Windows, Linux and BSD. * You must be able to run the test suite on Mac, Windows, Linux and BSD.
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful](http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs). To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
This requires installing [hub](https://github.com/github/hub). Both version 1 and 2 support `hub am -3`.
All code changes should be internal pull requests. All code changes should be internal pull requests.
Releases are tagged using [Semantic Versioning](http://semver.org/), which makes them available through gopkg.in. Releases are tagged using [Semantic Versioning](http://semver.org/).
[hub]: https://github.com/github/hub
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs

View File

View File

@ -8,14 +8,14 @@ Cross platform: Windows, Linux, BSD and OS X.
|Adapter |OS |Status | |Adapter |OS |Status |
|----------|----------|----------| |----------|----------|----------|
|inotify |Linux, Android\*|Supported| |inotify |Linux, Android\*|Supported [![Build Status](https://travis-ci.org/go-fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/go-fsnotify/fsnotify)|
|kqueue |BSD, OS X, iOS\*|Supported| |kqueue |BSD, OS X, iOS\*|Supported [![Circle CI](https://circleci.com/gh/go-fsnotify/fsnotify.svg?style=svg)](https://circleci.com/gh/go-fsnotify/fsnotify)|
|ReadDirectoryChangesW|Windows|Supported| |ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
|FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)| |FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)|
|FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)| |FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)|
|fanotify |Linux 2.6.37+ | | |fanotify |Linux 2.6.37+ | |
|USN Journals |Windows |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/53)|
|Polling |*All* |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)| |Polling |*All* |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)|
| |Plan 9 | |
\* Android and iOS are untested. \* Android and iOS are untested.
@ -41,8 +41,7 @@ import "gopkg.in/fsnotify.v1"
Further API changes are [planned](https://github.com/go-fsnotify/fsnotify/milestones), but a new major revision will be tagged, so you can depend on the v1 API. Further API changes are [planned](https://github.com/go-fsnotify/fsnotify/milestones), but a new major revision will be tagged, so you can depend on the v1 API.
**master** may have untagged changes. Use it to test the very latest code, **Master** may have unreleased changes. Use it to test the very latest code or when [contributing][], but don't expect it to remain API-compatible:
but don't expect it to remain API-compatible:
```go ```go
import "github.com/go-fsnotify/fsnotify" import "github.com/go-fsnotify/fsnotify"
@ -50,13 +49,11 @@ import "github.com/go-fsnotify/fsnotify"
## Contributing ## Contributing
* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). Please refer to [CONTRIBUTING][] before opening an issue or pull request.
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues). Please indicate the platform you are running on.
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/go-fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
Please read [CONTRIBUTING](https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md) before opening a pull request.
## Example ## Example
See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go). See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go).
[contributing]: https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md

26
Godeps/_workspace/src/gopkg.in/fsnotify.v1/circle.yml generated vendored Normal file
View File

@ -0,0 +1,26 @@
## OS X build (CircleCI iOS beta)
# Pretend like it's an Xcode project, at least to get it running.
machine:
environment:
XCODE_WORKSPACE: NotUsed.xcworkspace
XCODE_SCHEME: NotUsed
# This is where the go project is actually checked out to:
CIRCLE_BUILD_DIR: $HOME/.go_project/src/github.com/go-fsnotify/fsnotify
dependencies:
pre:
- brew upgrade go
test:
override:
- go test ./...
# Idealized future config, eventually with cross-platform build matrix :-)
# machine:
# go:
# version: 1.4
# os:
# - osx
# - linux

View File

@ -9,7 +9,7 @@ package fsnotify_test
import ( import (
"log" "log"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/gopkg.in/fsnotify.v1" "github.com/go-fsnotify/fsnotify"
) )
func ExampleNewWatcher() { func ExampleNewWatcher() {

View File

@ -9,6 +9,7 @@ package fsnotify
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -21,47 +22,66 @@ import (
type Watcher struct { type Watcher struct {
Events chan Event Events chan Event
Errors chan error Errors chan error
mu sync.Mutex // Map access mu sync.Mutex // Map access
fd int // File descriptor (as returned by the inotify_init() syscall) fd int
poller *fdPoller
watches map[string]*watch // Map of inotify watches (key: path) watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor) paths map[int]string // Map of watched paths (key: watch descriptor)
done chan bool // Channel for sending a "quit message" to the reader goroutine done chan struct{} // Channel for sending a "quit message" to the reader goroutine
isClosed bool // Set to true when Close() is first called doneResp chan struct{} // Channel to respond to Close
} }
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) { func NewWatcher() (*Watcher, error) {
// Create inotify fd
fd, errno := syscall.InotifyInit() fd, errno := syscall.InotifyInit()
if fd == -1 { if fd == -1 {
return nil, os.NewSyscallError("inotify_init", errno) return nil, errno
}
// Create epoll
poller, err := newFdPoller(fd)
if err != nil {
syscall.Close(fd)
return nil, err
} }
w := &Watcher{ w := &Watcher{
fd: fd, fd: fd,
watches: make(map[string]*watch), poller: poller,
paths: make(map[int]string), watches: make(map[string]*watch),
Events: make(chan Event), paths: make(map[int]string),
Errors: make(chan error), Events: make(chan Event),
done: make(chan bool, 1), Errors: make(chan error),
done: make(chan struct{}),
doneResp: make(chan struct{}),
} }
go w.readEvents() go w.readEvents()
return w, nil return w, nil
} }
func (w *Watcher) isClosed() bool {
select {
case <-w.done:
return true
default:
return false
}
}
// Close removes all watches and closes the events channel. // Close removes all watches and closes the events channel.
func (w *Watcher) Close() error { func (w *Watcher) Close() error {
if w.isClosed { if w.isClosed() {
return nil return nil
} }
w.isClosed = true
// Remove all watches // Send 'close' signal to goroutine, and set the Watcher to closed.
for name := range w.watches { close(w.done)
w.Remove(name)
}
// Send "quit" message to the reader goroutine // Wake up goroutine
w.done <- true w.poller.wake()
// Wait for goroutine to close
<-w.doneResp
return nil return nil
} }
@ -69,7 +89,7 @@ func (w *Watcher) Close() error {
// Add starts watching the named file or directory (non-recursively). // Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error { func (w *Watcher) Add(name string) error {
name = filepath.Clean(name) name = filepath.Clean(name)
if w.isClosed { if w.isClosed() {
return errors.New("inotify instance already closed") return errors.New("inotify instance already closed")
} }
@ -88,7 +108,7 @@ func (w *Watcher) Add(name string) error {
} }
wd, errno := syscall.InotifyAddWatch(w.fd, name, flags) wd, errno := syscall.InotifyAddWatch(w.fd, name, flags)
if wd == -1 { if wd == -1 {
return os.NewSyscallError("inotify_add_watch", errno) return errno
} }
w.mu.Lock() w.mu.Lock()
@ -99,20 +119,33 @@ func (w *Watcher) Add(name string) error {
return nil return nil
} }
// Remove stops watching the the named file or directory (non-recursively). // Remove stops watching the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error { func (w *Watcher) Remove(name string) error {
name = filepath.Clean(name) name = filepath.Clean(name)
// Fetch the watch.
w.mu.Lock() w.mu.Lock()
defer w.mu.Unlock() defer w.mu.Unlock()
watch, ok := w.watches[name] watch, ok := w.watches[name]
// Remove it from inotify.
if !ok { if !ok {
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
} }
// inotify_rm_watch will return EINVAL if the file has been deleted;
// the inotify will already have been removed.
// That means we can safely delete it from our watches, whatever inotify_rm_watch does.
delete(w.watches, name)
success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
if success == -1 { if success == -1 {
return os.NewSyscallError("inotify_rm_watch", errno) // TODO: Perhaps it's not helpful to return an error here in every case.
// the only two possible errors are:
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
return errno
} }
delete(w.watches, name)
return nil return nil
} }
@ -128,35 +161,65 @@ func (w *Watcher) readEvents() {
buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
n int // Number of bytes read with read() n int // Number of bytes read with read()
errno error // Syscall errno errno error // Syscall errno
ok bool // For poller.wait
) )
defer close(w.doneResp)
defer close(w.Errors)
defer close(w.Events)
defer syscall.Close(w.fd)
defer w.poller.close()
for { for {
// See if there is a message on the "done" channel // See if we have been closed.
select { if w.isClosed() {
case <-w.done:
syscall.Close(w.fd)
close(w.Events)
close(w.Errors)
return return
default: }
ok, errno = w.poller.wait()
if errno != nil {
select {
case w.Errors <- errno:
case <-w.done:
return
}
continue
}
if !ok {
continue
} }
n, errno = syscall.Read(w.fd, buf[:]) n, errno = syscall.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 == syscall.EINTR {
continue
}
// If EOF is received // syscall.Read might have been woken up by Close. If so, we're done.
if n == 0 { if w.isClosed() {
syscall.Close(w.fd)
close(w.Events)
close(w.Errors)
return return
} }
if n < 0 {
w.Errors <- os.NewSyscallError("read", errno)
continue
}
if n < syscall.SizeofInotifyEvent { if n < syscall.SizeofInotifyEvent {
w.Errors <- errors.New("inotify: short read in readEvents()") var err error
if n == 0 {
// If EOF is received. This should really never happen.
err = io.EOF
} else if n < 0 {
// If an error occured while reading.
err = errno
} else {
// Read was too short.
err = errors.New("notify: short read in readEvents()")
}
select {
case w.Errors <- err:
case <-w.done:
return
}
continue continue
} }
@ -187,7 +250,11 @@ func (w *Watcher) readEvents() {
// Send the events that are not ignored on the events channel // Send the events that are not ignored on the events channel
if !event.ignoreLinux(mask) { if !event.ignoreLinux(mask) {
w.Events <- event select {
case w.Events <- event:
case <-w.done:
return
}
} }
// Move to the next event in the buffer // Move to the next event in the buffer

View File

@ -0,0 +1,186 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"errors"
"syscall"
)
type fdPoller struct {
fd int // File descriptor (as returned by the inotify_init() syscall)
epfd int // Epoll file descriptor
pipe [2]int // Pipe for waking up
}
func emptyPoller(fd int) *fdPoller {
poller := new(fdPoller)
poller.fd = fd
poller.epfd = -1
poller.pipe[0] = -1
poller.pipe[1] = -1
return poller
}
// Create a new inotify poller.
// This creates an inotify handler, and an epoll handler.
func newFdPoller(fd int) (*fdPoller, error) {
var errno error
poller := emptyPoller(fd)
defer func() {
if errno != nil {
poller.close()
}
}()
poller.fd = fd
// Create epoll fd
poller.epfd, errno = syscall.EpollCreate(1)
if poller.epfd == -1 {
return nil, errno
}
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
errno = syscall.Pipe2(poller.pipe[:], syscall.O_NONBLOCK)
if errno != nil {
return nil, errno
}
// Register inotify fd with epoll
event := syscall.EpollEvent{
Fd: int32(poller.fd),
Events: syscall.EPOLLIN,
}
errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.fd, &event)
if errno != nil {
return nil, errno
}
// Register pipe fd with epoll
event = syscall.EpollEvent{
Fd: int32(poller.pipe[0]),
Events: syscall.EPOLLIN,
}
errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.pipe[0], &event)
if errno != nil {
return nil, errno
}
return poller, nil
}
// Wait using epoll.
// Returns true if something is ready to be read,
// false if there is not.
func (poller *fdPoller) wait() (bool, error) {
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
// I don't know whether epoll_wait returns the number of events returned,
// or the total number of events ready.
// I decided to catch both by making the buffer one larger than the maximum.
events := make([]syscall.EpollEvent, 7)
for {
n, errno := syscall.EpollWait(poller.epfd, events, -1)
if n == -1 {
if errno == syscall.EINTR {
continue
}
return false, errno
}
if n == 0 {
// If there are no events, try again.
continue
}
if n > 6 {
// This should never happen. More events were returned than should be possible.
return false, errors.New("epoll_wait returned more events than I know what to do with")
}
ready := events[:n]
epollhup := false
epollerr := false
epollin := false
for _, event := range ready {
if event.Fd == int32(poller.fd) {
if event.Events&syscall.EPOLLHUP != 0 {
// This should not happen, but if it does, treat it as a wakeup.
epollhup = true
}
if event.Events&syscall.EPOLLERR != 0 {
// If an error is waiting on the file descriptor, we should pretend
// something is ready to read, and let syscall.Read pick up the error.
epollerr = true
}
if event.Events&syscall.EPOLLIN != 0 {
// There is data to read.
epollin = true
}
}
if event.Fd == int32(poller.pipe[0]) {
if event.Events&syscall.EPOLLHUP != 0 {
// Write pipe descriptor was closed, by us. This means we're closing down the
// watcher, and we should wake up.
}
if event.Events&syscall.EPOLLERR != 0 {
// If an error is waiting on the pipe file descriptor.
// This is an absolute mystery, and should never ever happen.
return false, errors.New("Error on the pipe descriptor.")
}
if event.Events&syscall.EPOLLIN != 0 {
// This is a regular wakeup, so we have to clear the buffer.
err := poller.clearWake()
if err != nil {
return false, err
}
}
}
}
if epollhup || epollerr || epollin {
return true, nil
}
return false, nil
}
}
// Close the write end of the poller.
func (poller *fdPoller) wake() error {
buf := make([]byte, 1)
n, errno := syscall.Write(poller.pipe[1], buf)
if n == -1 {
if errno == syscall.EAGAIN {
// Buffer is full, poller will wake.
return nil
}
return errno
}
return nil
}
func (poller *fdPoller) clearWake() error {
// You have to be woken up a LOT in order to get to 100!
buf := make([]byte, 100)
n, errno := syscall.Read(poller.pipe[0], buf)
if n == -1 {
if errno == syscall.EAGAIN {
// Buffer is empty, someone else cleared our wake.
return nil
}
return errno
}
return nil
}
// Close all poller file descriptors, but not the one passed to it.
func (poller *fdPoller) close() {
if poller.pipe[1] != -1 {
syscall.Close(poller.pipe[1])
}
if poller.pipe[0] != -1 {
syscall.Close(poller.pipe[0])
}
if poller.epfd != -1 {
syscall.Close(poller.epfd)
}
}

View File

@ -0,0 +1,228 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"syscall"
"testing"
"time"
)
type testFd [2]int
func makeTestFd(t *testing.T) testFd {
var tfd testFd
errno := syscall.Pipe(tfd[:])
if errno != nil {
t.Fatalf("Failed to create pipe: %v", errno)
}
return tfd
}
func (tfd testFd) fd() int {
return tfd[0]
}
func (tfd testFd) closeWrite(t *testing.T) {
errno := syscall.Close(tfd[1])
if errno != nil {
t.Fatalf("Failed to close write end of pipe: %v", errno)
}
}
func (tfd testFd) put(t *testing.T) {
buf := make([]byte, 10)
_, errno := syscall.Write(tfd[1], buf)
if errno != nil {
t.Fatalf("Failed to write to pipe: %v", errno)
}
}
func (tfd testFd) get(t *testing.T) {
buf := make([]byte, 10)
_, errno := syscall.Read(tfd[0], buf)
if errno != nil {
t.Fatalf("Failed to read from pipe: %v", errno)
}
}
func (tfd testFd) close() {
syscall.Close(tfd[1])
syscall.Close(tfd[0])
}
func makePoller(t *testing.T) (testFd, *fdPoller) {
tfd := makeTestFd(t)
poller, err := newFdPoller(tfd.fd())
if err != nil {
t.Fatalf("Failed to create poller: %v", err)
}
return tfd, poller
}
func TestPollerWithBadFd(t *testing.T) {
_, err := newFdPoller(-1)
if err != syscall.EBADF {
t.Fatalf("Expected EBADF, got: %v", err)
}
}
func TestPollerWithData(t *testing.T) {
tfd, poller := makePoller(t)
defer tfd.close()
defer poller.close()
tfd.put(t)
ok, err := poller.wait()
if err != nil {
t.Fatalf("poller failed: %v", err)
}
if !ok {
t.Fatalf("expected poller to return true")
}
tfd.get(t)
}
func TestPollerWithWakeup(t *testing.T) {
tfd, poller := makePoller(t)
defer tfd.close()
defer poller.close()
err := poller.wake()
if err != nil {
t.Fatalf("wake failed: %v", err)
}
ok, err := poller.wait()
if err != nil {
t.Fatalf("poller failed: %v", err)
}
if ok {
t.Fatalf("expected poller to return false")
}
}
func TestPollerWithClose(t *testing.T) {
tfd, poller := makePoller(t)
defer tfd.close()
defer poller.close()
tfd.closeWrite(t)
ok, err := poller.wait()
if err != nil {
t.Fatalf("poller failed: %v", err)
}
if !ok {
t.Fatalf("expected poller to return true")
}
}
func TestPollerWithWakeupAndData(t *testing.T) {
tfd, poller := makePoller(t)
defer tfd.close()
defer poller.close()
tfd.put(t)
err := poller.wake()
if err != nil {
t.Fatalf("wake failed: %v", err)
}
// both data and wakeup
ok, err := poller.wait()
if err != nil {
t.Fatalf("poller failed: %v", err)
}
if !ok {
t.Fatalf("expected poller to return true")
}
// data is still in the buffer, wakeup is cleared
ok, err = poller.wait()
if err != nil {
t.Fatalf("poller failed: %v", err)
}
if !ok {
t.Fatalf("expected poller to return true")
}
tfd.get(t)
// data is gone, only wakeup now
err = poller.wake()
if err != nil {
t.Fatalf("wake failed: %v", err)
}
ok, err = poller.wait()
if err != nil {
t.Fatalf("poller failed: %v", err)
}
if ok {
t.Fatalf("expected poller to return false")
}
}
func TestPollerConcurrent(t *testing.T) {
tfd, poller := makePoller(t)
defer tfd.close()
defer poller.close()
oks := make(chan bool)
live := make(chan bool)
defer close(live)
go func() {
defer close(oks)
for {
ok, err := poller.wait()
if err != nil {
t.Fatalf("poller failed: %v", err)
}
oks <- ok
if !<-live {
return
}
}
}()
// Try a write
select {
case <-time.After(50 * time.Millisecond):
case <-oks:
t.Fatalf("poller did not wait")
}
tfd.put(t)
if !<-oks {
t.Fatalf("expected true")
}
tfd.get(t)
live <- true
// Try a wakeup
select {
case <-time.After(50 * time.Millisecond):
case <-oks:
t.Fatalf("poller did not wait")
}
err := poller.wake()
if err != nil {
t.Fatalf("wake failed: %v", err)
}
if <-oks {
t.Fatalf("expected false")
}
live <- true
// Try a close
select {
case <-time.After(50 * time.Millisecond):
case <-oks:
t.Fatalf("poller did not wait")
}
tfd.closeWrite(t)
if !<-oks {
t.Fatalf("expected true")
}
tfd.get(t)
}

View File

@ -0,0 +1,292 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"os"
"path/filepath"
"syscall"
"testing"
"time"
)
func TestInotifyCloseRightAway(t *testing.T) {
w, err := NewWatcher()
if err != nil {
t.Fatalf("Failed to create watcher")
}
// Close immediately; it won't even reach the first syscall.Read.
w.Close()
// Wait for the close to complete.
<-time.After(50 * time.Millisecond)
isWatcherReallyClosed(t, w)
}
func TestInotifyCloseSlightlyLater(t *testing.T) {
w, err := NewWatcher()
if err != nil {
t.Fatalf("Failed to create watcher")
}
// Wait until readEvents has reached syscall.Read, and Close.
<-time.After(50 * time.Millisecond)
w.Close()
// Wait for the close to complete.
<-time.After(50 * time.Millisecond)
isWatcherReallyClosed(t, w)
}
func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) {
testDir := tempMkdir(t)
defer os.RemoveAll(testDir)
w, err := NewWatcher()
if err != nil {
t.Fatalf("Failed to create watcher")
}
w.Add(testDir)
// Wait until readEvents has reached syscall.Read, and Close.
<-time.After(50 * time.Millisecond)
w.Close()
// Wait for the close to complete.
<-time.After(50 * time.Millisecond)
isWatcherReallyClosed(t, w)
}
func TestInotifyCloseAfterRead(t *testing.T) {
testDir := tempMkdir(t)
defer os.RemoveAll(testDir)
w, err := NewWatcher()
if err != nil {
t.Fatalf("Failed to create watcher")
}
err = w.Add(testDir)
if err != nil {
t.Fatalf("Failed to add .")
}
// Generate an event.
os.Create(filepath.Join(testDir, "somethingSOMETHINGsomethingSOMETHING"))
// Wait for readEvents to read the event, then close the watcher.
<-time.After(50 * time.Millisecond)
w.Close()
// Wait for the close to complete.
<-time.After(50 * time.Millisecond)
isWatcherReallyClosed(t, w)
}
func isWatcherReallyClosed(t *testing.T, w *Watcher) {
select {
case err, ok := <-w.Errors:
if ok {
t.Fatalf("w.Errors is not closed; readEvents is still alive after closing (error: %v)", err)
}
default:
t.Fatalf("w.Errors would have blocked; readEvents is still alive!")
}
select {
case _, ok := <-w.Events:
if ok {
t.Fatalf("w.Events is not closed; readEvents is still alive after closing")
}
default:
t.Fatalf("w.Events would have blocked; readEvents is still alive!")
}
}
func TestInotifyCloseCreate(t *testing.T) {
testDir := tempMkdir(t)
defer os.RemoveAll(testDir)
w, err := NewWatcher()
if err != nil {
t.Fatalf("Failed to create watcher: %v", err)
}
defer w.Close()
err = w.Add(testDir)
if err != nil {
t.Fatalf("Failed to add testDir: %v", err)
}
h, err := os.Create(filepath.Join(testDir, "testfile"))
if err != nil {
t.Fatalf("Failed to create file in testdir: %v", err)
}
h.Close()
select {
case _ = <-w.Events:
case err := <-w.Errors:
t.Fatalf("Error from watcher: %v", err)
case <-time.After(50 * time.Millisecond):
t.Fatalf("Took too long to wait for event")
}
// At this point, we've received one event, so the goroutine is ready.
// It's also blocking on syscall.Read.
// Now we try to swap the file descriptor under its nose.
w.Close()
w, err = NewWatcher()
defer w.Close()
if err != nil {
t.Fatalf("Failed to create second watcher: %v", err)
}
<-time.After(50 * time.Millisecond)
err = w.Add(testDir)
if err != nil {
t.Fatalf("Error adding testDir again: %v", err)
}
}
func TestInotifyStress(t *testing.T) {
testDir := tempMkdir(t)
defer os.RemoveAll(testDir)
testFile := filepath.Join(testDir, "testfile")
w, err := NewWatcher()
if err != nil {
t.Fatalf("Failed to create watcher: %v", err)
}
defer w.Close()
killchan := make(chan struct{})
defer close(killchan)
err = w.Add(testDir)
if err != nil {
t.Fatalf("Failed to add testDir: %v", err)
}
proc, err := os.FindProcess(os.Getpid())
if err != nil {
t.Fatalf("Error finding process: %v", err)
}
go func() {
for {
select {
case <-time.After(5 * time.Millisecond):
err := proc.Signal(syscall.SIGUSR1)
if err != nil {
t.Fatalf("Signal failed: %v", err)
}
case <-killchan:
return
}
}
}()
go func() {
for {
select {
case <-time.After(11 * time.Millisecond):
err := w.poller.wake()
if err != nil {
t.Fatalf("Wake failed: %v", err)
}
case <-killchan:
return
}
}
}()
go func() {
for {
select {
case <-killchan:
return
default:
handle, err := os.Create(testFile)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
handle.Close()
time.Sleep(time.Millisecond)
err = os.Remove(testFile)
if err != nil {
t.Fatalf("Remove failed: %v", err)
}
}
}
}()
creates := 0
removes := 0
after := time.After(5 * time.Second)
for {
select {
case <-after:
if creates-removes > 1 || creates-removes < -1 {
t.Fatalf("Creates and removes should not be off by more than one: %d creates, %d removes", creates, removes)
}
if creates < 50 {
t.Fatalf("Expected at least 50 creates, got %d", creates)
}
return
case err := <-w.Errors:
t.Fatalf("Got an error from watcher: %v", err)
case evt := <-w.Events:
if evt.Name != testFile {
t.Fatalf("Got an event for an unknown file: %s", evt.Name)
}
if evt.Op == Create {
creates++
}
if evt.Op == Remove {
removes++
}
}
}
}
func TestInotifyRemoveTwice(t *testing.T) {
testDir := tempMkdir(t)
defer os.RemoveAll(testDir)
testFile := filepath.Join(testDir, "testfile")
handle, err := os.Create(testFile)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
handle.Close()
w, err := NewWatcher()
if err != nil {
t.Fatalf("Failed to create watcher: %v", err)
}
defer w.Close()
err = w.Add(testFile)
if err != nil {
t.Fatalf("Failed to add testFile: %v", err)
}
err = os.Remove(testFile)
if err != nil {
t.Fatalf("Failed to remove testFile: %v", err)
}
err = w.Remove(testFile)
if err != syscall.EINVAL {
t.Fatalf("Expected EINVAL from Remove, got: %v", err)
}
err = w.Remove(testFile)
if err == syscall.EINVAL {
t.Fatalf("Got EINVAL again, watch was not removed")
}
}

View File

@ -1109,6 +1109,21 @@ func TestConcurrentRemovalOfWatch(t *testing.T) {
<-removed2 <-removed2
} }
func TestClose(t *testing.T) {
// Regression test for #59 bad file descriptor from Close
testDir := tempMkdir(t)
defer os.RemoveAll(testDir)
watcher := newWatcher(t)
if err := watcher.Add(testDir); err != nil {
t.Fatalf("Expected no error on Add, got %v", err)
}
err := watcher.Close()
if err != nil {
t.Fatalf("Expected no error on Close, got %v.", err)
}
}
func testRename(file1, file2 string) error { func testRename(file1, file2 string) error {
switch runtime.GOOS { switch runtime.GOOS {
case "windows", "plan9": case "windows", "plan9":

View File

@ -72,16 +72,20 @@ func (w *Watcher) Close() error {
w.isClosed = true w.isClosed = true
w.mu.Unlock() w.mu.Unlock()
// Send "quit" message to the reader goroutine:
w.done <- true
w.mu.Lock() w.mu.Lock()
ws := w.watches ws := w.watches
w.mu.Unlock() w.mu.Unlock()
var err error
for name := range ws { for name := range ws {
w.Remove(name) if e := w.Remove(name); e != nil && err == nil {
err = e
}
} }
// Send "quit" message to the reader goroutine:
w.done <- true
return nil return nil
} }