Commit cbed01e8 authored by Tom Bergan's avatar Tom Bergan Committed by Brad Fitzpatrick

http2: log Framer reads and writes when a server test fails

To help debug rare nondeterministic server_test failures, log Framer
reads and writes. Sample output from an injected test failure:

$ go test golang.org/x/net/http2
--- FAIL: TestServer_Request_Reject_Pseudo_scheme_invalid (0.00s)
    server_test.go:998: server request made it to handler; should've been rejected
    server_test.go:514: got a *http2.HeadersFrame; want *RSTStreamFrame
    server_test.go:229: Framer read log:
        2016-12-07 17:06:11.907199013 Framer 0xc4212665b0: read SETTINGS len=18, settings: MAX_FRAME_SIZE=1048576, MAX_CONCURRENT_STREAMS=250, MAX_HEADER_LIST_SIZE=1048896
        2016-12-07 17:06:11.907523124 Framer 0xc4212665b0: read SETTINGS flags=ACK len=0
        2016-12-07 17:06:11.908090453 Framer 0xc4212665b0: read HEADERS flags=END_STREAM|END_HEADERS stream=1 len=48
    server_test.go:235: Framer write log:
        2016-12-07 17:06:11.907152927 Framer 0xc4212665b0: wrote SETTINGS len=0
        2016-12-07 17:06:11.907207016 Framer 0xc4212665b0: wrote SETTINGS flags=ACK len=0
        2016-12-07 17:06:11.907550525 Framer 0xc4212665b0: wrote HEADERS flags=END_STREAM|END_HEADERS stream=1 len=16

Framer logs are written at the end of a test only if the test failed
and only if http2debug!=2. (If http2debug=2, then Framer logs are already
written to stdout.)

Hopefully this will help debug flaky tests. Or it might not.
The code is kind of ugly.

Updates golang/go#18235

Change-Id: I74c8ef82521d81f123663c98c883c71d9843964f
Reviewed-on: https://go-review.googlesource.com/34130
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 944c58e9
......@@ -317,10 +317,12 @@ type Framer struct {
// non-Continuation or Continuation on a different stream is
// attempted to be written.
logReads bool
logReads, logWrites bool
debugFramer *Framer // only use for logging written writes
debugFramerBuf *bytes.Buffer
debugFramer *Framer // only use for logging written writes
debugFramerBuf *bytes.Buffer
debugReadLoggerf func(string, ...interface{})
debugWriteLoggerf func(string, ...interface{})
}
func (fr *Framer) maxHeaderListSize() uint32 {
......@@ -355,7 +357,7 @@ func (f *Framer) endWrite() error {
byte(length>>16),
byte(length>>8),
byte(length))
if logFrameWrites {
if f.logWrites {
f.logWrite()
}
......@@ -378,10 +380,10 @@ func (f *Framer) logWrite() {
f.debugFramerBuf.Write(f.wbuf)
fr, err := f.debugFramer.ReadFrame()
if err != nil {
log.Printf("http2: Framer %p: failed to decode just-written frame", f)
f.debugWriteLoggerf("http2: Framer %p: failed to decode just-written frame", f)
return
}
log.Printf("http2: Framer %p: wrote %v", f, summarizeFrame(fr))
f.debugWriteLoggerf("http2: Framer %p: wrote %v", f, summarizeFrame(fr))
}
func (f *Framer) writeByte(v byte) { f.wbuf = append(f.wbuf, v) }
......@@ -399,9 +401,12 @@ const (
// NewFramer returns a Framer that writes frames to w and reads them from r.
func NewFramer(w io.Writer, r io.Reader) *Framer {
fr := &Framer{
w: w,
r: r,
logReads: logFrameReads,
w: w,
r: r,
logReads: logFrameReads,
logWrites: logFrameWrites,
debugReadLoggerf: log.Printf,
debugWriteLoggerf: log.Printf,
}
fr.getReadBuf = func(size uint32) []byte {
if cap(fr.readBuf) >= int(size) {
......@@ -483,7 +488,7 @@ func (fr *Framer) ReadFrame() (Frame, error) {
return nil, err
}
if fr.logReads {
log.Printf("http2: Framer %p: read %v", fr, summarizeFrame(f))
fr.debugReadLoggerf("http2: Framer %p: read %v", fr, summarizeFrame(f))
}
if fh.Type == FrameHeaders && fr.ReadMetaHeaders != nil {
return fr.readMetaFrame(f.(*HeadersFrame))
......@@ -1419,8 +1424,8 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
hdec.SetEmitEnabled(true)
hdec.SetMaxStringLength(fr.maxHeaderStringLen())
hdec.SetEmitFunc(func(hf hpack.HeaderField) {
if VerboseLogs && logFrameReads {
log.Printf("http2: decoded hpack field %+v", hf)
if VerboseLogs && fr.logReads {
fr.debugReadLoggerf("http2: decoded hpack field %+v", hf)
}
if !httplex.ValidHeaderFieldValue(hf.Value) {
invalid = headerFieldValueError(hf.Value)
......
......@@ -45,13 +45,22 @@ type serverTester struct {
t testing.TB
ts *httptest.Server
fr *Framer
logBuf *bytes.Buffer
logFilter []string // substrings to filter out
scMu sync.Mutex // guards sc
serverLogBuf bytes.Buffer // logger for httptest.Server
logFilter []string // substrings to filter out
scMu sync.Mutex // guards sc
sc *serverConn
hpackDec *hpack.Decoder
decodedHeaders [][2]string
// If http2debug!=2, then we capture Frame debug logs that will be written
// to t.Log after a test fails. The read and write logs use separate locks
// and buffers so we don't accidentally introduce synchronization between
// the read and write goroutines, which may hide data races.
frameReadLogMu sync.Mutex
frameReadLogBuf bytes.Buffer
frameWriteLogMu sync.Mutex
frameWriteLogBuf bytes.Buffer
// writing headers:
headerBuf bytes.Buffer
hpackEnc *hpack.Encoder
......@@ -75,7 +84,6 @@ var optQuiet = serverTesterOpt("quiet_logging")
func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}) *serverTester {
resetHooks()
logBuf := new(bytes.Buffer)
ts := httptest.NewUnstartedServer(handler)
tlsConfig := &tls.Config{
......@@ -110,9 +118,8 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}
ConfigureServer(ts.Config, h2server)
st := &serverTester{
t: t,
ts: ts,
logBuf: logBuf,
t: t,
ts: ts,
}
st.hpackEnc = hpack.NewEncoder(&st.headerBuf)
st.hpackDec = hpack.NewDecoder(initialHeaderTableSize, st.onHeaderField)
......@@ -121,7 +128,7 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}
if quiet {
ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
} else {
ts.Config.ErrorLog = log.New(io.MultiWriter(stderrv(), twriter{t: t, st: st}, logBuf), "", log.LstdFlags)
ts.Config.ErrorLog = log.New(io.MultiWriter(stderrv(), twriter{t: t, st: st}, &st.serverLogBuf), "", log.LstdFlags)
}
ts.StartTLS()
......@@ -142,6 +149,22 @@ func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}
}
st.cc = cc
st.fr = NewFramer(cc, cc)
if !logFrameReads && !logFrameWrites {
st.fr.debugReadLoggerf = func(m string, v ...interface{}) {
m = time.Now().Format("2006-01-02 15:04:05.999999999 ") + strings.TrimPrefix(m, "http2: ") + "\n"
st.frameReadLogMu.Lock()
fmt.Fprintf(&st.frameReadLogBuf, m, v...)
st.frameReadLogMu.Unlock()
}
st.fr.debugWriteLoggerf = func(m string, v ...interface{}) {
m = time.Now().Format("2006-01-02 15:04:05.999999999 ") + strings.TrimPrefix(m, "http2: ") + "\n"
st.frameWriteLogMu.Lock()
fmt.Fprintf(&st.frameWriteLogBuf, m, v...)
st.frameWriteLogMu.Unlock()
}
st.fr.logReads = true
st.fr.logWrites = true
}
}
return st
}
......@@ -201,6 +224,18 @@ func (st *serverTester) awaitIdle() {
func (st *serverTester) Close() {
if st.t.Failed() {
st.frameReadLogMu.Lock()
if st.frameReadLogBuf.Len() > 0 {
st.t.Logf("Framer read log:\n%s", st.frameReadLogBuf.String())
}
st.frameReadLogMu.Unlock()
st.frameWriteLogMu.Lock()
if st.frameWriteLogBuf.Len() > 0 {
st.t.Logf("Framer write log:\n%s", st.frameWriteLogBuf.String())
}
st.frameWriteLogMu.Unlock()
// If we failed already (and are likely in a Fatal,
// unwindowing), force close the connection, so the
// httptest.Server doesn't wait forever for the conn
......@@ -1100,10 +1135,10 @@ func TestServer_RejectsLargeFrames(t *testing.T) {
if gf.ErrCode != ErrCodeFrameSize {
t.Errorf("GOAWAY err = %v; want %v", gf.ErrCode, ErrCodeFrameSize)
}
if st.logBuf.Len() != 0 {
if st.serverLogBuf.Len() != 0 {
// Previously we spun here for a bit until the GOAWAY disconnect
// timer fired, logging while we fired.
t.Errorf("unexpected server output: %.500s\n", st.logBuf.Bytes())
t.Errorf("unexpected server output: %.500s\n", st.serverLogBuf.Bytes())
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment