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 }