Skip to content

Commit

Permalink
Merge pull request #3900 from hashicorp/fix-monitor-sigint-3891
Browse files Browse the repository at this point in the history
Fixes #3891: agent monitor no longer unresponsive before logs stream.
  • Loading branch information
banks authored Feb 21, 2018
2 parents c99e393 + de58eb1 commit 6da6e08
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 0 deletions.
4 changes: 4 additions & 0 deletions agent/agent_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,10 @@ func (s *HTTPServer) AgentMonitor(resp http.ResponseWriter, req *http.Request) (
defer s.agent.LogWriter.DeregisterHandler(handler)
notify := resp.(http.CloseNotifier).CloseNotify()

// Send header so client can start streaming body
resp.WriteHeader(http.StatusOK)
flusher.Flush()

// Stream logs until the connection is closed.
for {
select {
Expand Down
72 changes: 72 additions & 0 deletions command/monitor/monitor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package monitor

import (
"io"
"os"
"sync"
"testing"
"time"

"github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/logger"
"github.com/mitchellh/cli"
)

func TestMonitorCommand_exitssOnSignalBeforeLinesArrive(t *testing.T) {
t.Parallel()
logWriter := logger.NewLogWriter(512)
a := &agent.TestAgent{
Name: t.Name(),
LogWriter: logWriter,
LogOutput: io.MultiWriter(os.Stderr, logWriter),
}
a.Start()
defer a.Shutdown()

shutdownCh := make(chan struct{})

ui := cli.NewMockUi()
c := New(ui, shutdownCh)
// Only wait for ERR which shouldn't happen so should leave logs empty for a
// while
args := []string{"-http-addr=" + a.HTTPAddr(), "-log-level=ERR"}

// Buffer it so we don't deadlock when blocking send on shutdownCh triggers
// Run to return before we can select on it.
exitCode := make(chan int, 1)

// Run the monitor in another go routine. If this doesn't exit on our "signal"
// then the whole test will hang and we'll panic (to not blow up if people run
// the suite without -timeout)
var wg sync.WaitGroup
wg.Add(1)
go func() {
wg.Done() // Signal that this goroutine is at least running now
exitCode <- c.Run(args)
}()

// Wait for that routine to at least be running
wg.Wait()

// Simulate signal in a few milliseconds without blocking this thread
go func() {
time.Sleep(5 * time.Millisecond)
shutdownCh <- struct{}{}
}()

// Wait for a second - shouldn't take long to handle the signal before we
// panic. We simulate inside the select since the other goroutine might
// already have exited if there was some error and we'd block forever trying
// to write to unbuffered shutdownCh. We don't just buffer it because then it
// doesn't model the real shutdownCh we use for signal watching and could mask
// bugs in the handling.
select {
case ret := <-exitCode:
if ret != 0 {
t.Fatal("command returned with non-zero code")
}
// OK!
case <-time.After(1 * time.Second):
t.Fatal("timed out waiting for exit")
}
}

0 comments on commit 6da6e08

Please sign in to comment.