// Test that a connection is closed upon reaching an idle state if and only if the server // is shutting down. func TestCloseOnIdle(t *testing.T) { server := newServer() wg := helpers.NewWaitGroup() server.wg = wg fl := helpers.NewListener() runner := func() error { return server.Serve(fl) } startGenericServer(t, server, nil, runner) // Change to idle state while server is not closing; Close should not be called conn := &helpers.Conn{} server.ConnState(conn, http.StateIdle) if conn.CloseCalled { t.Error("Close was called unexpected") } server.Close() // wait until the server calls Close() on the listener // by that point the atomic closing variable will have been updated, avoiding a race. <-fl.CloseCalled conn = &helpers.Conn{} server.ConnState(conn, http.StateIdle) if !conn.CloseCalled { t.Error("Close was not called") } }
// Test that a request moving from active->idle->active using an actual // network connection still results in a corect shutdown func TestStateTransitionActiveIdleActive(t *testing.T) { server := newServer() wg := helpers.NewWaitGroup() statechanged := make(chan http.ConnState) server.wg = wg listener, exitchan := startServer(t, server, statechanged) client := newClient(listener.Addr(), false) client.Run() // wait for client to connect, but don't let it send the request if err := <-client.connected; err != nil { t.Fatal("Client failed to connect to server", err) } for i := 0; i < 2; i++ { client.sendrequest <- true waitForState(t, statechanged, http.StateActive, "Client failed to reach active state") <-client.idle client.idlerelease <- true waitForState(t, statechanged, http.StateIdle, "Client failed to reach idle state") } // client is now in an idle state server.Close() waiting := <-wg.WaitCalled if waiting != 0 { t.Errorf("Waitcount should be zero, got %d", waiting) } if err := <-exitchan; err != nil { t.Error("Unexpected error during shutdown", err) } }
// Tests that the server allows in-flight requests to complete // before shutting down. func TestGracefulness(t *testing.T) { server := newServer() wg := helpers.NewWaitGroup() server.wg = wg statechanged := make(chan http.ConnState) listener, exitchan := startServer(t, server, statechanged) client := newClient(listener.Addr(), false) client.Run() // wait for client to connect, but don't let it send the request yet if err := <-client.connected; err != nil { t.Fatal("Client failed to connect to server", err) } // avoid a race between the client connection and the server accept if state := <-statechanged; state != http.StateNew { t.Fatal("Unexpected state", state) } server.Close() waiting := <-wg.WaitCalled if waiting < 1 { t.Errorf("Expected the waitgroup to equal 1 at shutdown; actually %d", waiting) } // allow the client to finish sending the request and make sure the server exits after // (client will be in connected but idle state at that point) client.sendrequest <- true close(client.sendrequest) if err := <-exitchan; err != nil { t.Error("Unexpected error during shutdown", err) } }
// Tests that the server begins to shut down when told to and does not accept // new requests once shutdown has begun func TestShutdown(t *testing.T) { server := newServer() wg := helpers.NewWaitGroup() server.wg = wg statechanged := make(chan http.ConnState) listener, exitchan := startServer(t, server, statechanged) client1 := newClient(listener.Addr(), false) client1.Run() // wait for client1 to connect if err := <-client1.connected; err != nil { t.Fatal("Client failed to connect to server", err) } // avoid a race between the client connection and the server accept if state := <-statechanged; state != http.StateNew { t.Fatal("Unexpected state", state) } // start the shutdown; once it hits waitgroup.Wait() // the listener should of been closed, though client1 is still connected if server.Close() != true { t.Fatal("first call to Close returned false") } if server.Close() != false { t.Fatal("second call to Close returned true") } waiting := <-wg.WaitCalled if waiting != 1 { t.Errorf("Waitcount should be one, got %d", waiting) } // should get connection refused at this point client2 := newClient(listener.Addr(), false) client2.Run() if err := <-client2.connected; err == nil { t.Fatal("client2 connected when it should of received connection refused") } // let client1 finish so the server can exit close(client1.sendrequest) // don't bother sending an actual request <-exitchan }
func testStateTransition(t *testing.T, test transitionTest) { server := newServer() wg := helpers.NewWaitGroup() server.wg = wg startServer(t, server, nil) conn := &helpers.Conn{} for _, newState := range test.states { server.ConnState(conn, newState) } server.Close() waiting := <-wg.WaitCalled if waiting != test.expectedWgCount { names := make([]string, len(test.states)) for i, s := range test.states { names[i] = s.String() } transitions := strings.Join(names, " -> ") t.Errorf("%s - Waitcount should be %d, got %d", transitions, test.expectedWgCount, waiting) } }
// Test state transitions from new->active->-idle->closed using an actual // network connection and make sure the waitgroup count is correct at the end. func TestStateTransitionActiveIdleClosed(t *testing.T) { var ( listener net.Listener exitchan chan error ) keyFile, err1 := helpers.NewTempFile(helpers.Key) certFile, err2 := helpers.NewTempFile(helpers.Cert) defer keyFile.Unlink() defer certFile.Unlink() if err1 != nil || err2 != nil { t.Fatal("Failed to create temporary files", err1, err2) } for _, withTLS := range []bool{false, true} { server := newServer() wg := helpers.NewWaitGroup() statechanged := make(chan http.ConnState) server.wg = wg if withTLS { listener, exitchan = startTLSServer(t, server, certFile.Name(), keyFile.Name(), statechanged) } else { listener, exitchan = startServer(t, server, statechanged) } client := newClient(listener.Addr(), withTLS) client.Run() // wait for client to connect, but don't let it send the request if err := <-client.connected; err != nil { t.Fatal("Client failed to connect to server", err) } client.sendrequest <- true waitForState(t, statechanged, http.StateActive, "Client failed to reach active state") err := <-client.idle if err != nil { t.Fatalf("tls=%t unexpected error from client %s", withTLS, err) } client.idlerelease <- true waitForState(t, statechanged, http.StateIdle, "Client failed to reach idle state") // client is now in an idle state close(client.sendrequest) <-client.closed waitForState(t, statechanged, http.StateClosed, "Client failed to reach closed state") server.Close() waiting := <-wg.WaitCalled if waiting != 0 { t.Errorf("Waitcount should be zero, got %d", waiting) } if err := <-exitchan; err != nil { t.Error("Unexpected error during shutdown", err) } } }