func (f *fileListener) Close() error { f.Lock() select { case <-f.stop: f.Unlock() return nil // Already stopped default: close(f.stop) } var firstErr error if firstErr = f.netListener.Close(); firstErr != nil { slog.Trace("Error closing file listener: %v", firstErr) } for conn := range f.connections { if err := conn.Close(); err != nil { if firstErr == nil { firstErr = err } slog.Trace("Error closing connection: %v", err) } } f.Unlock() f.wg.Wait() close(f.changes) return firstErr }
// Collect file changes that happen FileChangeWindow ms from each // other, and restart all nodes in the process tree that match // features of the changed files. func start(tree *processtree.ProcessTree, filesChanged chan string, done, quit chan bool) { for { select { case <-quit: done <- true return case file := <-filesChanged: changed := make(map[string]bool) changed[file] = true slog.Trace("Restarter got the first file of potentially many") deadline := time.After(FileChangeWindow) deadline_expired := false for !deadline_expired { select { case <-quit: done <- true return case file := <-filesChanged: changed[file] = true case <-deadline: deadline_expired = true } } slog.Trace("Restarter has gathered %d changed files", len(changed)) go tree.RestartNodesWithFeatures(changed) } } }
func (f *fileListener) handleConnection(conn net.Conn, ch chan string) { // Handle writes stop := make(chan struct{}) go func() { for { select { case s := <-ch: conn.SetWriteDeadline(time.Now().Add(1 * time.Second)) if _, err := conn.Write([]byte(s + "\n")); err == io.EOF { return } else if err != nil { slog.Trace("Error writing to connection: %v", err) } case <-stop: return } } }() // Handle reads scanner := bufio.NewScanner(conn) for { if scanner.Scan() { f.changes <- scanner.Text() } else { if err := scanner.Err(); err != nil { select { case <-f.stop: break default: slog.Trace("Error reading from connection: %v", err) } } break } } f.Lock() defer f.Unlock() close(stop) delete(f.connections, conn) f.wg.Done() }
// Collect file changes that happen FileChangeWindow ms from each // other, and restart all nodes in the process tree that match // features of the changed files. func (r *restarter) start() { for { select { case <-r.quit: r.done <- true return case file := <-r.filesChanged: changed := make(map[string]bool) changed[file] = true slog.Trace("Restarter got the first file of potentially many") if r.gatherFiles(changed, time.After(FileChangeWindow)) { return } slog.Trace("Restarter has gathered %d changed files", len(changed)) go r.tree.RestartNodesWithFeatures(changed) } } }
func (s *SlaveNode) trace(format string, args ...interface{}) { if !slog.TraceEnabled() { return } _, file, line, _ := runtime.Caller(1) var prefix string if s.Pid != 0 { prefix = fmt.Sprintf("[%s:%d] %s/(%d)", file, line, s.Name, s.Pid) } else { prefix = fmt.Sprintf("[%s:%d] %s/(no PID)", file, line, s.Name) } new_args := make([]interface{}, len(args)+1) new_args[0] = prefix for i, v := range args { new_args[i+1] = v } slog.Trace("%s "+format, new_args...) }
func (f *fileListener) serve() { var tempDelay time.Duration // how long to sleep on accept failure for { conn, err := f.netListener.Accept() if err != nil { if ne, ok := err.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { tempDelay = 5 * time.Millisecond } else { tempDelay *= 2 } if max := 1 * time.Second; tempDelay > max { tempDelay = max } slog.Trace("filelistener: Accept error: %v; retrying in %v", err, tempDelay) time.Sleep(tempDelay) continue } select { case <-f.stop: return default: panic(err) } } ch := make(chan string) f.Lock() f.connections[conn] = ch f.wg.Add(1) f.Unlock() go f.handleConnection(conn, ch) } }