1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-06-30 18:13:54 +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",
"Comment": "v1.1.0",
"Rev": "f582d920d11386e8ae15227bb5933a8f9b4c3dec"
"Comment": "v1.2.0",
"Rev": "96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0"
},
{
"ImportPath": "gopkg.in/natefinch/lumberjack.v2",

View File

@ -1,10 +1,12 @@
sudo: false
language: go
go:
- 1.2
- tip
- 1.4.1
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:
- linux
- osx

View File

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

View File

@ -1,5 +1,15 @@
# 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
* kqueue: rework internals [#43](https://github.com/go-fsnotify/fsnotify/pull/43)
@ -10,7 +20,7 @@
* remove calls to os.NewSyscallError
* 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: 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
@ -81,6 +91,10 @@
* no tests for the current implementation
* 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
* [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
* 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).
* 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).
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`)
3. Ensure everything works and the tests pass (see below)
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`)
4. Create a new Pull Request on GitHub
If other team members need your patch before it is merged:
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.
This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/).
### Testing
@ -62,11 +67,11 @@ Help maintaining fsnotify is welcome. To be a maintainer:
* 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.
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).
This requires installing [hub](https://github.com/github/hub). Both version 1 and 2 support `hub am -3`.
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][].
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 |
|----------|----------|----------|
|inotify |Linux, Android\*|Supported|
|kqueue |BSD, OS X, iOS\*|Supported|
|ReadDirectoryChangesW|Windows|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 [![Circle CI](https://circleci.com/gh/go-fsnotify/fsnotify.svg?style=svg)](https://circleci.com/gh/go-fsnotify/fsnotify)|
|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)|
|FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)|
|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)|
| |Plan 9 | |
\* 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.
**master** may have untagged changes. Use it to test the very latest code,
but don't expect it to remain API-compatible:
**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:
```go
import "github.com/go-fsnotify/fsnotify"
@ -50,13 +49,11 @@ import "github.com/go-fsnotify/fsnotify"
## Contributing
* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com).
* 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.
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
## Example
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 (
"log"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/gopkg.in/fsnotify.v1"
"github.com/go-fsnotify/fsnotify"
)
func ExampleNewWatcher() {

View File

@ -9,6 +9,7 @@ package fsnotify
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
@ -21,47 +22,66 @@ import (
type Watcher struct {
Events chan Event
Errors chan error
mu sync.Mutex // Map access
fd int // File descriptor (as returned by the inotify_init() syscall)
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 bool // Channel for sending a "quit message" to the reader goroutine
isClosed bool // Set to true when Close() is first called
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.
func NewWatcher() (*Watcher, error) {
// Create inotify fd
fd, errno := syscall.InotifyInit()
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{
fd: fd,
watches: make(map[string]*watch),
paths: make(map[int]string),
Events: make(chan Event),
Errors: make(chan error),
done: make(chan bool, 1),
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{}),
}
go w.readEvents()
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.
func (w *Watcher) Close() error {
if w.isClosed {
if w.isClosed() {
return nil
}
w.isClosed = true
// Remove all watches
for name := range w.watches {
w.Remove(name)
}
// Send 'close' signal to goroutine, and set the Watcher to closed.
close(w.done)
// Send "quit" message to the reader goroutine
w.done <- true
// Wake up goroutine
w.poller.wake()
// Wait for goroutine to close
<-w.doneResp
return nil
}
@ -69,7 +89,7 @@ func (w *Watcher) Close() error {
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
name = filepath.Clean(name)
if w.isClosed {
if w.isClosed() {
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)
if wd == -1 {
return os.NewSyscallError("inotify_add_watch", errno)
return errno
}
w.mu.Lock()
@ -99,20 +119,33 @@ func (w *Watcher) Add(name string) error {
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 {
name = filepath.Clean(name)
// Fetch the watch.
w.mu.Lock()
defer w.mu.Unlock()
watch, ok := w.watches[name]
// Remove it from inotify.
if !ok {
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)
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
}
@ -128,35 +161,65 @@ func (w *Watcher) readEvents() {
buf [syscall.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 syscall.Close(w.fd)
defer w.poller.close()
for {
// See if there is a message on the "done" channel
select {
case <-w.done:
syscall.Close(w.fd)
close(w.Events)
close(w.Errors)
// See if we have been closed.
if w.isClosed() {
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[:])
// 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
if n == 0 {
syscall.Close(w.fd)
close(w.Events)
close(w.Errors)
// syscall.Read might have been woken up by Close. If so, we're done.
if w.isClosed() {
return
}
if n < 0 {
w.Errors <- os.NewSyscallError("read", errno)
continue
}
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
}
@ -187,7 +250,11 @@ func (w *Watcher) readEvents() {
// Send the events that are not ignored on the events channel
if !event.ignoreLinux(mask) {
w.Events <- event
select {
case w.Events <- event:
case <-w.done:
return
}
}
// 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
}
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 {
switch runtime.GOOS {
case "windows", "plan9":

View File

@ -72,16 +72,20 @@ func (w *Watcher) Close() error {
w.isClosed = true
w.mu.Unlock()
// Send "quit" message to the reader goroutine:
w.done <- true
w.mu.Lock()
ws := w.watches
w.mu.Unlock()
var err error
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
}