Skip to content

net/http/httptest: race on Server.Close and long-running requests #12262

@jmhodges

Description

@jmhodges

httptest.Server races between requests coming into its mux, and Server.Close. Normal usage doesn't typically trigger the race detector, but when a test client timeouts (we do this intentionally in letsencrypt/boulder to test how boulder fails) and moves on to teardown the test with Close, a race is found easily.

This code has a client that always times out when making a request to the server and reliably has a race detected: https://play.golang.org/p/aC0YTPBYGn

This is its output when run with the race detector:

GET error: Get http://127.0.0.1:61653: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
==================
WARNING: DATA RACE
Write by main goroutine:
  sync.raceWrite()
      /Users/jmhodges/projects/go/src/sync/race.go:41 +0x2e
  sync.(*WaitGroup).Wait()
      /Users/jmhodges/projects/go/src/sync/waitgroup.go:124 +0xf9
  net/http/httptest.(*Server).Close()
      /Users/jmhodges/projects/go/src/net/http/httptest/server.go:168 +0x80
  main.main()
      /Users/jmhodges/projects/foo.go:35 +0x1fb

Previous read by goroutine 10:
  sync.raceRead()
      /Users/jmhodges/projects/go/src/sync/race.go:37 +0x2e
  sync.(*WaitGroup).Add()
      /Users/jmhodges/projects/go/src/sync/waitgroup.go:66 +0xfa
  net/http/httptest.(*waitGroupHandler).ServeHTTP()
      /Users/jmhodges/projects/go/src/net/http/httptest/server.go:198 +0x5c
  net/http.serverHandler.ServeHTTP()
      /Users/jmhodges/projects/go/src/net/http/server.go:1862 +0x206
  net/http.(*conn).serve()
      /Users/jmhodges/projects/go/src/net/http/server.go:1361 +0x117c

Goroutine 10 (running) created at:
  net/http.(*Server).Serve()
      /Users/jmhodges/projects/go/src/net/http/server.go:1910 +0x464
==================
Found 1 data race(s)

It seems the race detector is saying that between Server.Close closing the listener and Server.Close calling WaitGroup.Wait, a new Request could be pulled off the listener, but that the new Request could then only get to waitGroupHandler.ServeHTTP's WaitGroup.Add after the Wait is called.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions