logmower-shipper/vendor/github.com/jtagcat/util/tail/tail_single.go

95 lines
1.7 KiB
Go

package tail
import (
"context"
"sync"
"github.com/fsnotify/fsnotify"
)
// Unstable, beta
func New(ctx context.Context, name string, offset int64, whence int) (<-chan *Line, <-chan error, error) {
w, err := fsnotify.NewWatcher()
if err != nil {
return nil, nil, err
}
if err := w.Add(name); err != nil {
return nil, nil, err
}
lineChan, errChan := make(chan *Line), make(chan error)
go singleFile(ctx, w, &Tailable{
Name: name, Offset: offset, Whence: whence,
}, lineChan, errChan)
return lineChan, errChan, nil
}
// assumes file exists
func singleFile(ctx context.Context,
w *fsnotify.Watcher, file *Tailable,
lineChan chan<- *Line, errChan chan<- error,
) {
var wg sync.WaitGroup
sctx, cancel := context.WithCancel(ctx)
file.wakeup = make(chan struct{}, 1)
wg.Add(1)
go func() {
// no need to lock/unlock orderedLineChan, as we only have one same-named file across its life
fileHandle(sctx, *file, true, &orderedLines{c: lineChan}, errChan)
wg.Done()
}()
defer func() {
w.Close()
cancel()
wg.Wait()
close(lineChan)
close(errChan)
}()
for {
select {
case <-ctx.Done():
errChan <- ctx.Err()
return
default:
}
// There is no priority in select.
// To prevent race of looping with expired context,
// ctx.Done() is checked again at the start.
select {
case <-ctx.Done():
continue
case err, ok := <-w.Errors:
if ok {
errChan <- err
}
return
case ev, ok := <-w.Events:
if !ok {
return
}
switch ev.Op {
case fsnotify.Write:
select {
case file.wakeup <- struct{}{}:
default:
}
case fsnotify.Remove, fsnotify.Rename:
close(file.wakeup)
return
}
}
}
}