Skip to content

Commit 03c24c2

Browse files
committed
http2: use synthetic time in server tests
Change newServerTester to return a server using fake time and a fake net.Conn. Change-Id: I9d5db0cbe75696aed6d99ff1cd2369c2dea426c3 Reviewed-on: https://go-review.googlesource.com/c/net/+/586247 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
1 parent 022530c commit 03c24c2

7 files changed

+318
-156
lines changed

http2/http2.go

+13
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package http2 // import "golang.org/x/net/http2"
1717

1818
import (
1919
"bufio"
20+
"context"
2021
"crypto/tls"
2122
"fmt"
2223
"io"
@@ -26,6 +27,7 @@ import (
2627
"strconv"
2728
"strings"
2829
"sync"
30+
"time"
2931

3032
"golang.org/x/net/http/httpguts"
3133
)
@@ -377,3 +379,14 @@ func validPseudoPath(v string) bool {
377379
// makes that struct also non-comparable, and generally doesn't add
378380
// any size (as long as it's first).
379381
type incomparable [0]func()
382+
383+
// synctestGroupInterface is the methods of synctestGroup used by Server and Transport.
384+
// It's defined as an interface here to let us keep synctestGroup entirely test-only
385+
// and not a part of non-test builds.
386+
type synctestGroupInterface interface {
387+
Join()
388+
Now() time.Time
389+
NewTimer(d time.Duration) timer
390+
AfterFunc(d time.Duration, f func()) timer
391+
ContextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc)
392+
}

http2/server.go

+65-22
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,39 @@ type Server struct {
154154
// so that we don't embed a Mutex in this struct, which will make the
155155
// struct non-copyable, which might break some callers.
156156
state *serverInternalState
157+
158+
// Synchronization group used for testing.
159+
// Outside of tests, this is nil.
160+
group synctestGroupInterface
161+
}
162+
163+
func (s *Server) markNewGoroutine() {
164+
if s.group != nil {
165+
s.group.Join()
166+
}
167+
}
168+
169+
func (s *Server) now() time.Time {
170+
if s.group != nil {
171+
return s.group.Now()
172+
}
173+
return time.Now()
174+
}
175+
176+
// newTimer creates a new time.Timer, or a synthetic timer in tests.
177+
func (s *Server) newTimer(d time.Duration) timer {
178+
if s.group != nil {
179+
return s.group.NewTimer(d)
180+
}
181+
return timeTimer{time.NewTimer(d)}
182+
}
183+
184+
// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
185+
func (s *Server) afterFunc(d time.Duration, f func()) timer {
186+
if s.group != nil {
187+
return s.group.AfterFunc(d, f)
188+
}
189+
return timeTimer{time.AfterFunc(d, f)}
157190
}
158191

159192
func (s *Server) initialConnRecvWindowSize() int32 {
@@ -400,6 +433,10 @@ func (o *ServeConnOpts) handler() http.Handler {
400433
//
401434
// The opts parameter is optional. If nil, default values are used.
402435
func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
436+
s.serveConn(c, opts, nil)
437+
}
438+
439+
func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverConn)) {
403440
baseCtx, cancel := serverConnBaseContext(c, opts)
404441
defer cancel()
405442

@@ -426,6 +463,9 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
426463
pushEnabled: true,
427464
sawClientPreface: opts.SawClientPreface,
428465
}
466+
if newf != nil {
467+
newf(sc)
468+
}
429469

430470
s.state.registerConn(sc)
431471
defer s.state.unregisterConn(sc)
@@ -599,8 +639,8 @@ type serverConn struct {
599639
inFrameScheduleLoop bool // whether we're in the scheduleFrameWrite loop
600640
needToSendGoAway bool // we need to schedule a GOAWAY frame write
601641
goAwayCode ErrCode
602-
shutdownTimer *time.Timer // nil until used
603-
idleTimer *time.Timer // nil if unused
642+
shutdownTimer timer // nil until used
643+
idleTimer timer // nil if unused
604644

605645
// Owned by the writeFrameAsync goroutine:
606646
headerWriteBuf bytes.Buffer
@@ -649,12 +689,12 @@ type stream struct {
649689
flow outflow // limits writing from Handler to client
650690
inflow inflow // what the client is allowed to POST/etc to us
651691
state streamState
652-
resetQueued bool // RST_STREAM queued for write; set by sc.resetStream
653-
gotTrailerHeader bool // HEADER frame for trailers was seen
654-
wroteHeaders bool // whether we wrote headers (not status 100)
655-
readDeadline *time.Timer // nil if unused
656-
writeDeadline *time.Timer // nil if unused
657-
closeErr error // set before cw is closed
692+
resetQueued bool // RST_STREAM queued for write; set by sc.resetStream
693+
gotTrailerHeader bool // HEADER frame for trailers was seen
694+
wroteHeaders bool // whether we wrote headers (not status 100)
695+
readDeadline timer // nil if unused
696+
writeDeadline timer // nil if unused
697+
closeErr error // set before cw is closed
658698

659699
trailer http.Header // accumulated trailers
660700
reqTrailer http.Header // handler's Request.Trailer
@@ -811,6 +851,7 @@ type readFrameResult struct {
811851
// consumer is done with the frame.
812852
// It's run on its own goroutine.
813853
func (sc *serverConn) readFrames() {
854+
sc.srv.markNewGoroutine()
814855
gate := make(chan struct{})
815856
gateDone := func() { gate <- struct{}{} }
816857
for {
@@ -843,6 +884,7 @@ type frameWriteResult struct {
843884
// At most one goroutine can be running writeFrameAsync at a time per
844885
// serverConn.
845886
func (sc *serverConn) writeFrameAsync(wr FrameWriteRequest, wd *writeData) {
887+
sc.srv.markNewGoroutine()
846888
var err error
847889
if wd == nil {
848890
err = wr.write.writeFrame(sc)
@@ -922,13 +964,13 @@ func (sc *serverConn) serve() {
922964
sc.setConnState(http.StateIdle)
923965

924966
if sc.srv.IdleTimeout > 0 {
925-
sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
967+
sc.idleTimer = sc.srv.afterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
926968
defer sc.idleTimer.Stop()
927969
}
928970

929971
go sc.readFrames() // closed by defer sc.conn.Close above
930972

931-
settingsTimer := time.AfterFunc(firstSettingsTimeout, sc.onSettingsTimer)
973+
settingsTimer := sc.srv.afterFunc(firstSettingsTimeout, sc.onSettingsTimer)
932974
defer settingsTimer.Stop()
933975

934976
loopNum := 0
@@ -1057,10 +1099,10 @@ func (sc *serverConn) readPreface() error {
10571099
errc <- nil
10581100
}
10591101
}()
1060-
timer := time.NewTimer(prefaceTimeout) // TODO: configurable on *Server?
1102+
timer := sc.srv.newTimer(prefaceTimeout) // TODO: configurable on *Server?
10611103
defer timer.Stop()
10621104
select {
1063-
case <-timer.C:
1105+
case <-timer.C():
10641106
return errPrefaceTimeout
10651107
case err := <-errc:
10661108
if err == nil {
@@ -1425,7 +1467,7 @@ func (sc *serverConn) goAway(code ErrCode) {
14251467

14261468
func (sc *serverConn) shutDownIn(d time.Duration) {
14271469
sc.serveG.check()
1428-
sc.shutdownTimer = time.AfterFunc(d, sc.onShutdownTimer)
1470+
sc.shutdownTimer = sc.srv.afterFunc(d, sc.onShutdownTimer)
14291471
}
14301472

14311473
func (sc *serverConn) resetStream(se StreamError) {
@@ -2022,7 +2064,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
20222064
// (in Go 1.8), though. That's a more sane option anyway.
20232065
if sc.hs.ReadTimeout > 0 {
20242066
sc.conn.SetReadDeadline(time.Time{})
2025-
st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
2067+
st.readDeadline = sc.srv.afterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
20262068
}
20272069

20282070
return sc.scheduleHandler(id, rw, req, handler)
@@ -2120,7 +2162,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
21202162
st.flow.add(sc.initialStreamSendWindowSize)
21212163
st.inflow.init(sc.srv.initialStreamRecvWindowSize())
21222164
if sc.hs.WriteTimeout > 0 {
2123-
st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
2165+
st.writeDeadline = sc.srv.afterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
21242166
}
21252167

21262168
sc.streams[id] = st
@@ -2344,6 +2386,7 @@ func (sc *serverConn) handlerDone() {
23442386

23452387
// Run on its own goroutine.
23462388
func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) {
2389+
sc.srv.markNewGoroutine()
23472390
defer sc.sendServeMsg(handlerDoneMsg)
23482391
didPanic := true
23492392
defer func() {
@@ -2640,7 +2683,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
26402683
var date string
26412684
if _, ok := rws.snapHeader["Date"]; !ok {
26422685
// TODO(bradfitz): be faster here, like net/http? measure.
2643-
date = time.Now().UTC().Format(http.TimeFormat)
2686+
date = rws.conn.srv.now().UTC().Format(http.TimeFormat)
26442687
}
26452688

26462689
for _, v := range rws.snapHeader["Trailer"] {
@@ -2762,7 +2805,7 @@ func (rws *responseWriterState) promoteUndeclaredTrailers() {
27622805

27632806
func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
27642807
st := w.rws.stream
2765-
if !deadline.IsZero() && deadline.Before(time.Now()) {
2808+
if !deadline.IsZero() && deadline.Before(w.rws.conn.srv.now()) {
27662809
// If we're setting a deadline in the past, reset the stream immediately
27672810
// so writes after SetWriteDeadline returns will fail.
27682811
st.onReadTimeout()
@@ -2778,17 +2821,17 @@ func (w *responseWriter) SetReadDeadline(deadline time.Time) error {
27782821
if deadline.IsZero() {
27792822
st.readDeadline = nil
27802823
} else if st.readDeadline == nil {
2781-
st.readDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onReadTimeout)
2824+
st.readDeadline = sc.srv.afterFunc(deadline.Sub(w.rws.conn.srv.now()), st.onReadTimeout)
27822825
} else {
2783-
st.readDeadline.Reset(deadline.Sub(time.Now()))
2826+
st.readDeadline.Reset(deadline.Sub(w.rws.conn.srv.now()))
27842827
}
27852828
})
27862829
return nil
27872830
}
27882831

27892832
func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
27902833
st := w.rws.stream
2791-
if !deadline.IsZero() && deadline.Before(time.Now()) {
2834+
if !deadline.IsZero() && deadline.Before(w.rws.conn.srv.now()) {
27922835
// If we're setting a deadline in the past, reset the stream immediately
27932836
// so writes after SetWriteDeadline returns will fail.
27942837
st.onWriteTimeout()
@@ -2804,9 +2847,9 @@ func (w *responseWriter) SetWriteDeadline(deadline time.Time) error {
28042847
if deadline.IsZero() {
28052848
st.writeDeadline = nil
28062849
} else if st.writeDeadline == nil {
2807-
st.writeDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onWriteTimeout)
2850+
st.writeDeadline = sc.srv.afterFunc(deadline.Sub(w.rws.conn.srv.now()), st.onWriteTimeout)
28082851
} else {
2809-
st.writeDeadline.Reset(deadline.Sub(time.Now()))
2852+
st.writeDeadline.Reset(deadline.Sub(w.rws.conn.srv.now()))
28102853
}
28112854
})
28122855
return nil

http2/server_push_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func TestServer_Push_Success(t *testing.T) {
105105
errc <- fmt.Errorf("unknown RequestURL %q", r.URL.RequestURI())
106106
}
107107
})
108-
stURL = st.ts.URL
108+
stURL = "https://" + st.authority()
109109

110110
// Send one request, which should push two responses.
111111
st.greet()
@@ -169,7 +169,7 @@ func TestServer_Push_Success(t *testing.T) {
169169
return checkPushPromise(f, 2, [][2]string{
170170
{":method", "GET"},
171171
{":scheme", "https"},
172-
{":authority", st.ts.Listener.Addr().String()},
172+
{":authority", st.authority()},
173173
{":path", "/pushed?get"},
174174
{"user-agent", userAgent},
175175
})
@@ -178,7 +178,7 @@ func TestServer_Push_Success(t *testing.T) {
178178
return checkPushPromise(f, 4, [][2]string{
179179
{":method", "HEAD"},
180180
{":scheme", "https"},
181-
{":authority", st.ts.Listener.Addr().String()},
181+
{":authority", st.authority()},
182182
{":path", "/pushed?head"},
183183
{"cookie", cookie},
184184
{"user-agent", userAgent},

0 commit comments

Comments
 (0)