Commit bd68b8ab authored by Caio Marcelo de Oliveira Filho's avatar Caio Marcelo de Oliveira Filho Committed by Brad Fitzpatrick

net/http: TimeoutHandler should start timer when serving request

TimeoutHandler was starting the Timer when the handler was created,
instead of when serving a request. It also was sharing it between
multiple requests, which is incorrect, as the requests might start
at different times.

Store the timeout duration and create the Timer when ServeHTTP is
called. Different requests will have different timers.

The testing plumbing was simplified to store the channel used to
control when timeout happens. It overrides the regular timer.

Fixes #14568.

Change-Id: I4bd51a83f412396f208682d3ae5e382db5f8dc81
Reviewed-on: https://go-review.googlesource.com/20046Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 6c4e90a9
...@@ -59,9 +59,9 @@ func SetTestHookServerServe(fn func(*Server, net.Listener)) { testHookServerServ ...@@ -59,9 +59,9 @@ func SetTestHookServerServe(fn func(*Server, net.Listener)) { testHookServerServ
func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler { func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler {
return &timeoutHandler{ return &timeoutHandler{
handler: handler, handler: handler,
timeout: func() <-chan time.Time { return ch }, testTimeout: ch,
// (no body and nil cancelTimer) // (no body)
} }
} }
......
...@@ -1888,6 +1888,32 @@ func TestTimeoutHandlerRaceHeaderTimeout(t *testing.T) { ...@@ -1888,6 +1888,32 @@ func TestTimeoutHandlerRaceHeaderTimeout(t *testing.T) {
} }
} }
// Issue 14568.
func TestTimeoutHandlerStartTimerWhenServing(t *testing.T) {
if testing.Short() {
t.Skip("skipping sleeping test in -short mode")
}
defer afterTest(t)
var handler HandlerFunc = func(w ResponseWriter, _ *Request) {
w.WriteHeader(StatusNoContent)
}
timeout := 300 * time.Millisecond
ts := httptest.NewServer(TimeoutHandler(handler, timeout, ""))
defer ts.Close()
// Issue was caused by the timeout handler starting the timer when
// was created, not when the request. So wait for more than the timeout
// to ensure that's not the case.
time.Sleep(2 * timeout)
res, err := Get(ts.URL)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != StatusNoContent {
t.Errorf("got res.StatusCode %d, want %v", res.StatusCode, StatusNoContent)
}
}
// Verifies we don't path.Clean() on the wrong parts in redirects. // Verifies we don't path.Clean() on the wrong parts in redirects.
func TestRedirectMunging(t *testing.T) { func TestRedirectMunging(t *testing.T) {
req, _ := NewRequest("GET", "http://example.com/", nil) req, _ := NewRequest("GET", "http://example.com/", nil)
......
...@@ -2309,15 +2309,10 @@ func (srv *Server) onceSetNextProtoDefaults() { ...@@ -2309,15 +2309,10 @@ func (srv *Server) onceSetNextProtoDefaults() {
// TimeoutHandler buffers all Handler writes to memory and does not // TimeoutHandler buffers all Handler writes to memory and does not
// support the Hijacker or Flusher interfaces. // support the Hijacker or Flusher interfaces.
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler { func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler {
t := time.NewTimer(dt)
return &timeoutHandler{ return &timeoutHandler{
handler: h, handler: h,
body: msg, body: msg,
dt: dt,
// Effectively storing a *time.Timer, but decomposed
// for testing:
timeout: func() <-chan time.Time { return t.C },
cancelTimer: t.Stop,
} }
} }
...@@ -2328,12 +2323,11 @@ var ErrHandlerTimeout = errors.New("http: Handler timeout") ...@@ -2328,12 +2323,11 @@ var ErrHandlerTimeout = errors.New("http: Handler timeout")
type timeoutHandler struct { type timeoutHandler struct {
handler Handler handler Handler
body string body string
dt time.Duration
// timeout returns the channel of a *time.Timer and // When set, no timer will be created and this channel will
// cancelTimer cancels it. They're stored separately for // be used instead.
// testing purposes. testTimeout <-chan time.Time
timeout func() <-chan time.Time // returns channel producing a timeout
cancelTimer func() bool // optional
} }
func (h *timeoutHandler) errorBody() string { func (h *timeoutHandler) errorBody() string {
...@@ -2344,6 +2338,12 @@ func (h *timeoutHandler) errorBody() string { ...@@ -2344,6 +2338,12 @@ func (h *timeoutHandler) errorBody() string {
} }
func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) { func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
var t *time.Timer
timeout := h.testTimeout
if timeout == nil {
t = time.NewTimer(h.dt)
timeout = t.C
}
done := make(chan struct{}) done := make(chan struct{})
tw := &timeoutWriter{ tw := &timeoutWriter{
w: w, w: w,
...@@ -2363,10 +2363,10 @@ func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) { ...@@ -2363,10 +2363,10 @@ func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
} }
w.WriteHeader(tw.code) w.WriteHeader(tw.code)
w.Write(tw.wbuf.Bytes()) w.Write(tw.wbuf.Bytes())
if h.cancelTimer != nil { if t != nil {
h.cancelTimer() t.Stop()
} }
case <-h.timeout(): case <-timeout:
tw.mu.Lock() tw.mu.Lock()
defer tw.mu.Unlock() defer tw.mu.Unlock()
w.WriteHeader(StatusServiceUnavailable) w.WriteHeader(StatusServiceUnavailable)
......
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