Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net/http/httptest: (*Server).Close races with Serve #12789

Closed
bcmills opened this issue Sep 29, 2015 · 5 comments
Closed

net/http/httptest: (*Server).Close races with Serve #12789

bcmills opened this issue Sep 29, 2015 · 5 comments

Comments

@bcmills
Copy link
Contributor

bcmills commented Sep 29, 2015

See https://go-review.googlesource.com/#/c/15047/, which works around this race in one particular instance - you can reproduce this error prior to that change by running context/ctxhttp/ctxhttp_test under the race detector.

Close makes this sequence of calls:

// Close shuts down the server and blocks until all outstanding
// requests on this server have completed.
func (s *Server) Close() {
  s.Listener.Close()
  s.wg.Wait()

The call to s.Listener.Close prevents new connections from being established, but it does not do anything to stop existing connections.

Calling Start on the server calls (*Server).wrapHandler, which injects a waitGroupHandler. Its ServeHTTP method calls s.wg.Add:

func (h *waitGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  h.s.wg.Add(1)
  defer h.s.wg.Done()
  h.h.ServeHTTP(w, r)
}

ServeHTTP is called after the listener's Accept has already returned, so Close may already have been called in the meantime and the call to h.s.wg.Add races with the s.wg.Wait there.

I believe that the simplest fix (which is unfortunately not all that simple) is to wrap the net.Listener, not just the http.Handler - that is, waitGroupHandler should actually be a {net.Listener, http.Handler} pair. The presence of any outstanding Accept call should block Close from returning until the wg.Add call has completed (e.g. by locking a sync.Mutex before calling Accept on the wrapped listener and locking that same mutex before returning from Close). It will take some care to avoid introducing deadlocks or further races.

(*waitGroupHandler).ServeHTTP can't do the synchronization itself: httptest guarantees that "Close shuts down the server and blocks until all outstanding requests on this server have completed", and allowing a ServeHTTP call to proceed after Close (even to write an arbitrary "already closed" error) would violate that guarantee.

@bradfitz
Copy link
Contributor

I think this is a dup of #12781 ?

@bradfitz
Copy link
Contributor

Close makes this sequence of calls:
...
but it does not do anything to stop existing connections.

You quote two lines of that method, but not the immediately-following lines:

        s.CloseClientConnections()
        if t, ok := http.DefaultTransport.(*http.Transport); ok {
                t.CloseIdleConnections()
        }

And then claim the method does nothing to existing connections?

@bcmills
Copy link
Contributor Author

bcmills commented Sep 29, 2015

You quote two lines of that method, but not the immediately-following lines:
[...]
And then claim the method does nothing to existing connections?

I was imprecise: it doesn't do anything to stop them before calling Wait, which is what matters for the race.

@bcmills
Copy link
Contributor Author

bcmills commented Sep 29, 2015

And yes, this looks like the same underlying issue as #12781.

@gopherbot
Copy link

CL https://golang.org/cl/15151 mentions this issue.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants