"crypto/tls"
"encoding/json"
"errors"
+ "flag"
"fmt"
"internal/testenv"
"io"
}
}
+var slowTests = flag.Bool("slow", false, "run slow tests")
+
+func TestServerShutdownStateNew(t *testing.T) {
+ if !*slowTests {
+ t.Skip("skipping slow test without -slow flag")
+ }
+ setParallel(t)
+ defer afterTest(t)
+
+ ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
+ // nothing.
+ }))
+ defer ts.Close()
+
+ // Start a connection but never write to it.
+ c, err := net.Dial("tcp", ts.Listener.Addr().String())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer c.Close()
+
+ shutdownRes := make(chan error, 1)
+ go func() {
+ shutdownRes <- ts.Config.Shutdown(context.Background())
+ }()
+ readRes := make(chan error, 1)
+ go func() {
+ _, err := c.Read([]byte{0})
+ readRes <- err
+ }()
+
+ const expectTimeout = 5 * time.Second
+ t0 := time.Now()
+ select {
+ case got := <-shutdownRes:
+ d := time.Since(t0)
+ if got != nil {
+ t.Fatalf("shutdown error after %v: %v", d, err)
+ }
+ if d < expectTimeout/2 {
+ t.Errorf("shutdown too soon after %v", d)
+ }
+ case <-time.After(expectTimeout * 3 / 2):
+ t.Fatalf("timeout waiting for shutdown")
+ }
+
+ // Wait for c.Read to unblock; should be already done at this point,
+ // or within a few milliseconds.
+ select {
+ case err := <-readRes:
+ if err == nil {
+ t.Error("expected error from Read")
+ }
+ case <-time.After(2 * time.Second):
+ t.Errorf("timeout waiting for Read to unblock")
+ }
+}
+
// Issue 17878: tests that we can call Close twice.
func TestServerCloseDeadlock(t *testing.T) {
var s Server
curReq atomic.Value // of *response (which has a Request in it)
- curState atomic.Value // of ConnState
+ curState struct{ atomic uint64 } // packed (unixtime<<8|uint8(ConnState))
// mu guards hijackedv
mu sync.Mutex
case StateHijacked, StateClosed:
srv.trackConn(c, false)
}
- c.curState.Store(connStateInterface[state])
+ if state > 0xff || state < 0 {
+ panic("internal error")
+ }
+ packedState := uint64(time.Now().Unix()<<8) | uint64(state)
+ atomic.StoreUint64(&c.curState.atomic, packedState)
if hook := srv.ConnState; hook != nil {
hook(nc, state)
}
}
-// connStateInterface is an array of the interface{} versions of
-// ConnState values, so we can use them in atomic.Values later without
-// paying the cost of shoving their integers in an interface{}.
-var connStateInterface = [...]interface{}{
- StateNew: StateNew,
- StateActive: StateActive,
- StateIdle: StateIdle,
- StateHijacked: StateHijacked,
- StateClosed: StateClosed,
+func (c *conn) getState() (state ConnState, unixSec int64) {
+ packedState := atomic.LoadUint64(&c.curState.atomic)
+ return ConnState(packedState & 0xff), int64(packedState >> 8)
}
// badRequestError is a literal string (used by in the server in HTML,
defer s.mu.Unlock()
quiescent := true
for c := range s.activeConn {
- st, ok := c.curState.Load().(ConnState)
- if !ok || st != StateIdle {
+ st, unixSec := c.getState()
+ // Issue 22682: treat StateNew connections as if
+ // they're idle if we haven't read the first request's
+ // header in over 5 seconds.
+ if st == StateNew && unixSec < time.Now().Unix()-5 {
+ st = StateIdle
+ }
+ if st != StateIdle || unixSec == 0 {
+ // Assume unixSec == 0 means it's a very new
+ // connection, without state set yet.
quiescent = false
continue
}