func (s *fslockSuite) TestTomb(c *gc.C) { const timeToDie = 200 * time.Millisecond die := tomb.Tomb{} dir := c.MkDir() lock, err := fslock.NewLock(dir, "testing", s.lockConfig) c.Assert(err, gc.IsNil) // Just use one lock, and try to lock it twice. err = lock.Lock("very busy") c.Assert(err, gc.IsNil) checkTomb := func() error { select { case <-die.Dying(): return tomb.ErrDying default: // no-op to fall through to return. } return nil } go func() { time.Sleep(timeToDie) die.Killf("time to die") }() err = lock.LockWithFunc("won't happen", checkTomb) c.Assert(errors.Cause(err), gc.Equals, tomb.ErrDying) msg, err := lock.Message() c.Assert(err, gc.IsNil) c.Assert(msg, gc.Equals, "very busy") }
func (fw *InotifyFileWatcher) BlockUntilExists(t tomb.Tomb) error { w, err := fsnotify.NewWatcher() if err != nil { return err } defer w.Close() dirname := filepath.Dir(fw.Filename) // Watch for new files to be created in the parent directory. err = w.WatchFlags(dirname, fsnotify.FSN_CREATE) if err != nil { return err } defer w.RemoveWatch(filepath.Dir(fw.Filename)) // Do a real check now as the file might have been created before // calling `WatchFlags` above. if _, err = os.Stat(fw.Filename); !os.IsNotExist(err) { // file exists, or stat returned an error. return err } for { select { case evt := <-w.Event: if evt.Name == fw.Filename { return nil } case <-t.Dying(): return tomb.ErrDying } } panic("unreachable") }
func (fw *InotifyFileWatcher) ChangeEvents(t tomb.Tomb, fi os.FileInfo) chan bool { w, err := fsnotify.NewWatcher() if err != nil { panic(err) } err = w.Watch(fw.Filename) if err != nil { panic(err) } ch := make(chan bool) fw.Size = fi.Size() go func() { defer w.Close() defer w.RemoveWatch(fw.Filename) defer close(ch) for { prevSize := fw.Size var evt *fsnotify.FileEvent select { case evt = <-w.Event: case <-t.Dying(): return } switch { case evt.IsDelete(): fallthrough case evt.IsRename(): return case evt.IsModify(): fi, err := os.Stat(fw.Filename) if err != nil { // XXX: no panic here panic(err) } fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { return } // send only if channel is empty. select { case ch <- true: default: } } } }() return ch }
func (fw *InotifyFileWatcher) ChangeEvents(t tomb.Tomb, fi os.FileInfo) *FileChanges { changes := NewFileChanges() w, err := fsnotify.NewWatcher() if err != nil { panic(err) } err = w.Watch(fw.Filename) if err != nil { panic(err) } fw.Size = fi.Size() go func() { defer w.Close() defer w.RemoveWatch(fw.Filename) defer changes.Close() for { prevSize := fw.Size var evt *fsnotify.FileEvent select { case evt = <-w.Event: case <-t.Dying(): return } switch { case evt.IsDelete(): fallthrough case evt.IsRename(): changes.NotifyDeleted() return case evt.IsModify(): fi, err := os.Stat(fw.Filename) if err != nil { // XXX: no panic here panic(err) } fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { changes.NotifyTruncated() } else { changes.NotifyModified() } prevSize = fw.Size } } }() return changes }
func (fw *PollingFileWatcher) ChangeEvents(t *tomb.Tomb, origFi os.FileInfo) *FileChanges { changes := NewFileChanges() var prevModTime time.Time // XXX: use tomb.Tomb to cleanly manage these goroutines. replace // the fatal (below) with tomb's Kill. fw.Size = origFi.Size() go func() { defer changes.Close() prevSize := fw.Size for { select { case <-t.Dying(): return default: } time.Sleep(POLL_DURATION) fi, err := os.Stat(fw.Filename) if err != nil { if os.IsNotExist(err) { // File does not exist (has been deleted). changes.NotifyDeleted() return } // XXX: report this error back to the user util.Fatal("Failed to stat file %v: %v", fw.Filename, err) } // File got moved/renamed? if !os.SameFile(origFi, fi) { changes.NotifyDeleted() return } // File got truncated? fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { changes.NotifyTruncated() prevSize = fw.Size continue } prevSize = fw.Size // File was appended to (changed)? modTime := fi.ModTime() if modTime != prevModTime { prevModTime = modTime changes.NotifyModified() } } }() return changes }
// Stop stops the watcher. If an error is returned by the // watcher, t is killed with the error. func Stop(w Stopper, t *tomb.Tomb) { if err := w.Stop(); err != nil { if err != tomb.ErrStillAlive && err != tomb.ErrDying { // tomb.Kill() checks for the two errors above // by value, so we shouldn't wrap them, but we // wrap any other error. err = errors.Trace(err) } t.Kill(err) } }
func (fw *PollingFileWatcher) BlockUntilExists(t tomb.Tomb) error { for { if _, err := os.Stat(fw.Filename); err == nil { return nil } else if !os.IsNotExist(err) { return err } select { case <-time.After(POLL_DURATION): continue case <-t.Dying(): return tomb.ErrDying } } panic("unreachable") }
// delayedTomb returns a tomb that starts dying a given duration // after t starts dying. func delayedTomb(t *tomb.Tomb, d time.Duration) *tomb.Tomb { var delayed tomb.Tomb go func() { select { case <-t.Dying(): time.Sleep(d) delayed.Kill(nil) case <-delayed.Dying(): return } }() return &delayed }
// copyEvents copies channel events from "in" to "out", coalescing. func copyEvents(out chan<- struct{}, in <-chan struct{}, tomb *tomb.Tomb) { var outC chan<- struct{} for { select { case <-tomb.Dying(): return case _, ok := <-in: if !ok { return } outC = out case outC <- struct{}{}: outC = nil } } }
func testState(t *testing.T, tb *tomb.Tomb, wantDying, wantDead bool, wantErr error) { select { case <-tb.Dying(): if !wantDying { t.Error("<-Dying: should block") } default: if wantDying { t.Error("<-Dying: should not block") } } seemsDead := false select { case <-tb.Dead(): if !wantDead { t.Error("<-Dead: should block") } seemsDead = true default: if wantDead { t.Error("<-Dead: should not block") } } if err := tb.Err(); err != wantErr { t.Errorf("Err: want %#v, got %#v", wantErr, err) } if wantDead && seemsDead { waitErr := tb.Wait() switch { case waitErr == tomb.ErrStillAlive: t.Errorf("Wait should not return ErrStillAlive") case !reflect.DeepEqual(waitErr, wantErr): t.Errorf("Wait: want %#v, got %#v", wantErr, waitErr) } } }
// propagateTearDown tears down the handler, but ensures any error is // propagated through the tomb's Kill method. func propagateTearDown(handler tearDowner, t *tomb.Tomb) { if err := handler.TearDown(); err != nil { t.Kill(err) } }
// Stop stops the watcher. If an error is returned by the // watcher, t is killed with the error. func Stop(w Stopper, t *tomb.Tomb) { if err := w.Stop(); err != nil { t.Kill(err) } }
func (fw *PollingFileWatcher) ChangeEvents(t tomb.Tomb, origFi os.FileInfo) chan bool { ch := make(chan bool) stop := make(chan bool) var once sync.Once var prevModTime time.Time // XXX: use tomb.Tomb to cleanly manage these goroutines. replace // the panic (below) with tomb's Kill. stopAndClose := func() { go func() { close(ch) stop <- true }() } fw.Size = origFi.Size() go func() { prevSize := fw.Size for { select { case <-stop: return case <-t.Dying(): once.Do(stopAndClose) continue default: } time.Sleep(POLL_DURATION) fi, err := os.Stat(fw.Filename) if err != nil { if os.IsNotExist(err) { once.Do(stopAndClose) continue } /// XXX: do not panic here. panic(err) } // File got moved/rename within POLL_DURATION? if !os.SameFile(origFi, fi) { once.Do(stopAndClose) continue } // Was the file truncated? fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { once.Do(stopAndClose) continue } // If the file was changed since last check, notify. modTime := fi.ModTime() if modTime != prevModTime { prevModTime = modTime select { case ch <- true: default: } } } }() return ch }
func (fw *InotifyFileWatcher) ChangeEvents(t *tomb.Tomb, fi os.FileInfo) *FileChanges { changes := NewFileChanges() w, err := inotifyTracker.NewWatcher() if err != nil { util.Fatal("Error creating fsnotify watcher: %v", err) } err = w.Watch(fw.Filename) if err != nil { util.Fatal("Error watching %v: %v", fw.Filename, err) } fw.Size = fi.Size() go func() { defer inotifyTracker.CloseWatcher(w) defer changes.Close() for { prevSize := fw.Size var evt *fsnotify.FileEvent var ok bool select { case evt, ok = <-w.Event: if !ok { return } case <-t.Dying(): return } switch { case evt.IsDelete(): fallthrough case evt.IsRename(): changes.NotifyDeleted() return case evt.IsModify(): fi, err := os.Stat(fw.Filename) if err != nil { if os.IsNotExist(err) { changes.NotifyDeleted() return } // XXX: report this error back to the user util.Fatal("Failed to stat file %v: %v", fw.Filename, err) } fw.Size = fi.Size() if prevSize > 0 && prevSize > fw.Size { changes.NotifyTruncated() } else { changes.NotifyModified() } prevSize = fw.Size } } }() return changes }
// TearDown the handler, but ensure any error is propagated func handlerTearDown(handler WatchHandler, t *tomb.Tomb) { if err := handler.TearDown(); err != nil { t.Kill(err) } }