github.com/nycdavid/zeus@v0.0.0-20201208104106-9ba439429e03/go/filemonitor/filelistener.go (about)

     1  package filemonitor
     2  
     3  import (
     4  	"bufio"
     5  	"io"
     6  	"net"
     7  	"sync"
     8  	"time"
     9  
    10  	slog "github.com/burke/zeus/go/shinylog"
    11  )
    12  
    13  type fileListener struct {
    14  	gatheringMonitor
    15  	netListener net.Listener
    16  	connections map[net.Conn]chan string
    17  	stop        chan struct{}
    18  	sync.Mutex
    19  	wg sync.WaitGroup
    20  }
    21  
    22  func NewFileListener(fileChangeDelay time.Duration, ln net.Listener) FileMonitor {
    23  	fl := fileListener{
    24  		netListener: ln,
    25  		connections: make(map[net.Conn]chan string),
    26  		stop:        make(chan struct{}),
    27  	}
    28  	fl.fileChangeDelay = fileChangeDelay
    29  	fl.changes = make(chan string)
    30  
    31  	go fl.serveListeners()
    32  	go fl.serve()
    33  
    34  	return &fl
    35  }
    36  
    37  func (f *fileListener) Add(file string) error {
    38  	f.Lock()
    39  	defer f.Unlock()
    40  
    41  	for _, ch := range f.connections {
    42  		ch <- file
    43  	}
    44  
    45  	return nil
    46  }
    47  
    48  func (f *fileListener) Close() error {
    49  	f.Lock()
    50  
    51  	select {
    52  	case <-f.stop:
    53  		f.Unlock()
    54  		return nil // Already stopped
    55  	default:
    56  		close(f.stop)
    57  	}
    58  
    59  	var firstErr error
    60  	if firstErr = f.netListener.Close(); firstErr != nil {
    61  		slog.Trace("Error closing file listener: %v", firstErr)
    62  	}
    63  
    64  	for conn := range f.connections {
    65  		if err := conn.Close(); err != nil {
    66  			if firstErr == nil {
    67  				firstErr = err
    68  			}
    69  			slog.Trace("Error closing connection: %v", err)
    70  		}
    71  	}
    72  
    73  	f.Unlock()
    74  	f.wg.Wait()
    75  	close(f.changes)
    76  
    77  	return firstErr
    78  }
    79  
    80  func (f *fileListener) serve() {
    81  	var tempDelay time.Duration // how long to sleep on accept failure
    82  
    83  	for {
    84  		conn, err := f.netListener.Accept()
    85  		if err != nil {
    86  			if ne, ok := err.(net.Error); ok && ne.Temporary() {
    87  				if tempDelay == 0 {
    88  					tempDelay = 5 * time.Millisecond
    89  				} else {
    90  					tempDelay *= 2
    91  				}
    92  				if max := 1 * time.Second; tempDelay > max {
    93  					tempDelay = max
    94  				}
    95  				slog.Trace("filelistener: Accept error: %v; retrying in %v", err, tempDelay)
    96  				time.Sleep(tempDelay)
    97  				continue
    98  			}
    99  
   100  			select {
   101  			case <-f.stop:
   102  				return
   103  			default:
   104  				panic(err)
   105  			}
   106  		}
   107  
   108  		ch := make(chan string)
   109  		f.Lock()
   110  		f.connections[conn] = ch
   111  		f.wg.Add(1)
   112  		f.Unlock()
   113  
   114  		go f.handleConnection(conn, ch)
   115  	}
   116  }
   117  
   118  func (f *fileListener) handleConnection(conn net.Conn, ch chan string) {
   119  	// Handle writes
   120  	stop := make(chan struct{})
   121  	go func() {
   122  		for {
   123  			select {
   124  			case s := <-ch:
   125  				conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
   126  				if _, err := conn.Write([]byte(s + "\n")); err == io.EOF {
   127  					return
   128  				} else if err != nil {
   129  					slog.Trace("Error writing to connection: %v", err)
   130  				}
   131  			case <-stop:
   132  				return
   133  			}
   134  		}
   135  	}()
   136  
   137  	// Handle reads
   138  	scanner := bufio.NewScanner(conn)
   139  	for {
   140  		if scanner.Scan() {
   141  			f.changes <- scanner.Text()
   142  		} else {
   143  			if err := scanner.Err(); err != nil {
   144  				select {
   145  				case <-f.stop:
   146  					break
   147  				default:
   148  					slog.Trace("Error reading from connection: %v", err)
   149  				}
   150  			}
   151  			break
   152  		}
   153  	}
   154  
   155  	f.Lock()
   156  	defer f.Unlock()
   157  
   158  	close(stop)
   159  	delete(f.connections, conn)
   160  	f.wg.Done()
   161  }