Skip to content

Instantly share code, notes, and snippets.

@rcrowley
Created February 28, 2014 02:01

Revisions

  1. rcrowley created this gist Feb 28, 2014.
    117 changes: 117 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,117 @@
    Benchmark of two graceful stop implementations
    ==============================================

    The server for each of these benchmarks is based on <https://github.com/rcrowley/go-tigertonic/tree/master/example>. It was run as `./example >/dev/null 2>/dev/null`.

    The client for each of these benchmarks is `ab -H"Host: example.com" -c"100" -n"1000000" "http://127.0.0.1:8000/stuff/ID"`.

    Baseline
    --------

    As a baseline, I removed the `chan struct{}` and `sync.WaitGroup` from [`server.go`](https://github.com/rcrowley/go-tigertonic/blob/master/server.go) and built the binary against Go tip.

    ```
    Concurrency Level: 100
    Time taken for tests: 235.452 seconds
    Complete requests: 1000000
    Failed requests: 0
    Write errors: 0
    Total transferred: 136000000 bytes
    HTML transferred: 28000000 bytes
    Requests per second: 4247.15 [#/sec] (mean)
    Time per request: 23.545 [ms] (mean)
    Time per request: 0.235 [ms] (mean, across all concurrent requests)
    Transfer rate: 564.07 [Kbytes/sec] received
    Connection Times (ms)
    min mean[+/-sd] median max
    Connect: 0 0 0.7 0 18
    Processing: 0 23 8.5 21 252
    Waiting: 0 23 8.5 21 252
    Total: 0 24 8.5 22 252
    Percentage of the requests served within a certain time (ms)
    50% 22
    66% 23
    75% 25
    80% 26
    90% 30
    95% 37
    98% 47
    99% 62
    100% 252 (longest request)
    ```

    CL 67730046
    -----------

    This benchmark is of CL 67730046 that adds an `int32` (accessed via `sync/atomic`) and a `sync.WaitGroup` to `net/http` and uses `bufio.Buffer.Peek` to preempt keepalive connections.

    ```
    Concurrency Level: 100
    Time taken for tests: 228.575 seconds
    Complete requests: 1000000
    Failed requests: 0
    Write errors: 0
    Total transferred: 136000000 bytes
    HTML transferred: 28000000 bytes
    Requests per second: 4374.94 [#/sec] (mean)
    Time per request: 22.857 [ms] (mean)
    Time per request: 0.229 [ms] (mean, across all concurrent requests)
    Transfer rate: 581.05 [Kbytes/sec] received
    Connection Times (ms)
    min mean[+/-sd] median max
    Connect: 0 0 0.6 0 17
    Processing: 1 23 7.9 21 209
    Waiting: 1 22 7.8 21 209
    Total: 1 23 7.9 21 209
    Percentage of the requests served within a certain time (ms)
    50% 21
    66% 23
    75% 24
    80% 25
    90% 28
    95% 34
    98% 45
    99% 57
    100% 209 (longest request)
    ```

    CL 69260044
    -----------

    This benchmark is of CL 69260044 and uses `http.Server.ConnState` to manage a `chan struct{}`, `sync.WaitGroup`, and a `map[string]net.Conn` to preempt keepalive connections.

    ```
    Concurrency Level: 100
    Time taken for tests: 249.333 seconds
    Complete requests: 1000000
    Failed requests: 0
    Write errors: 0
    Total transferred: 136000000 bytes
    HTML transferred: 28000000 bytes
    Requests per second: 4010.70 [#/sec] (mean)
    Time per request: 24.933 [ms] (mean)
    Time per request: 0.249 [ms] (mean, across all concurrent requests)
    Transfer rate: 532.67 [Kbytes/sec] received
    Connection Times (ms)
    min mean[+/-sd] median max
    Connect: 0 0 0.8 0 70
    Processing: 0 25 9.8 23 384
    Waiting: 0 24 9.8 22 384
    Total: 0 25 9.9 23 384
    Percentage of the requests served within a certain time (ms)
    50% 23
    66% 25
    75% 26
    80% 27
    90% 31
    95% 40
    98% 51
    99% 65
    100% 384 (longest request)
    ```
    149 changes: 149 additions & 0 deletions server-67730046.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,149 @@
    package tigertonic

    import (
    "crypto/tls"
    "crypto/x509"
    "io/ioutil"
    "net"
    "net/http"
    )

    // Server is an http.Server with better defaults.
    type Server struct {
    http.Server
    listener net.Listener
    }

    // NewServer returns an http.Server with better defaults.
    func NewServer(addr string, handler http.Handler) *Server {
    s := &Server{
    Server: http.Server{
    Addr: addr,
    Handler: &serverHandler{
    Handler: handler,
    //ch: ch,
    },
    MaxHeaderBytes: 4096,
    ReadTimeout: 60e9, // These are absolute times which must be
    WriteTimeout: 60e9, // longer than the longest {up,down}load.
    },
    }
    return s
    }

    // NewTLSServer returns an http.Server with better defaults configured to use
    // the certificate and private key files.
    func NewTLSServer(
    addr, cert, key string,
    handler http.Handler,
    ) (*Server, error) {
    s := NewServer(addr, handler)
    return s, s.TLS(cert, key)
    }

    // CA overrides the certificate authority on the server's TLSConfig field.
    func (s *Server) CA(ca string) error {
    certPool := x509.NewCertPool()
    buf, err := ioutil.ReadFile(ca)
    if nil != err {
    return err
    }
    certPool.AppendCertsFromPEM(buf)
    s.tlsConfig()
    s.TLSConfig.RootCAs = certPool
    return nil
    }

    // ClientCA configures the CA pool for verifying client side certificates.
    func (s *Server) ClientCA(ca string) error {
    certPool := x509.NewCertPool()
    buf, err := ioutil.ReadFile(ca)
    if nil != err {
    return err
    }
    certPool.AppendCertsFromPEM(buf)
    s.tlsConfig()
    s.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
    s.TLSConfig.ClientCAs = certPool
    return nil
    }

    // Close closes the listener the server is using and signals open connections
    // to close at their earliest convenience. Then it waits for all open
    // connections to become closed.
    func (s *Server) Close() error {
    return s.Server.Close()
    }

    // ListenAndServe calls net.Listen with s.Addr and then calls s.Serve.
    func (s *Server) ListenAndServe() error {
    addr := s.Addr
    if "" == addr {
    if nil == s.TLSConfig {
    addr = ":http"
    } else {
    addr = ":https"
    }
    }
    l, err := net.Listen("tcp", addr)
    if nil != err {
    return err
    }
    if nil != s.TLSConfig {
    l = tls.NewListener(l, s.TLSConfig)
    }
    return s.Serve(l)
    }

    // ListenAndServeTLS calls s.TLS with the given certificate and private key
    // files and then calls s.ListenAndServe.
    func (s *Server) ListenAndServeTLS(cert, key string) error {
    s.TLS(cert, key)
    return s.ListenAndServe()
    }

    // Serve behaves like http.Server.Serve with the added option to stop the
    // server gracefully with the s.Close method.
    func (s *Server) Serve(l net.Listener) error {
    s.listener = l
    return s.Server.Serve(s.listener)
    }

    // TLS configures this server to be a TLS server using the given certificate
    // and private key files.
    func (s *Server) TLS(cert, key string) error {
    c, err := tls.LoadX509KeyPair(cert, key)
    if nil != err {
    return err
    }
    s.tlsConfig()
    s.TLSConfig.Certificates = []tls.Certificate{c}
    return nil
    }

    func (s *Server) tlsConfig() {
    if nil == s.TLSConfig {
    s.TLSConfig = &tls.Config{
    CipherSuites: []uint16{
    tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
    tls.TLS_RSA_WITH_RC4_128_SHA,
    },
    NextProtos: []string{"http/1.1"},
    }
    }
    }

    type serverHandler struct {
    http.Handler
    }

    func (h *serverHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // r.Header.Set("Host", r.Host) // Should I?
    r.URL.Host = r.Host
    if nil != r.TLS {
    r.URL.Scheme = "https"
    } else {
    r.URL.Scheme = "http"
    }
    h.Handler.ServeHTTP(w, r)
    }
    187 changes: 187 additions & 0 deletions server-69260044.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,187 @@
    package tigertonic

    import (
    "crypto/tls"
    "crypto/x509"
    "io/ioutil"
    "net"
    "net/http"
    "sync"
    )

    // Server is an http.Server with better defaults.
    type Server struct {
    http.Server
    ch chan<- struct{}
    conns map[string]net.Conn
    listener net.Listener
    mu sync.Mutex // guards conns
    waitGroup sync.WaitGroup
    }

    // NewServer returns an http.Server with better defaults.
    func NewServer(addr string, handler http.Handler) *Server {
    ch := make(chan struct{})
    s := &Server{
    Server: http.Server{
    Addr: addr,
    Handler: &serverHandler{
    Handler: handler,
    },
    MaxHeaderBytes: 4096,
    ReadTimeout: 60e9, // These are absolute times which must be
    WriteTimeout: 60e9, // longer than the longest {up,down}load.
    },
    ch: ch,
    conns: make(map[string]net.Conn),
    }
    s.ConnState = func(conn net.Conn, state http.ConnState) {
    switch state {
    case http.StateNew:
    s.waitGroup.Add(1)
    case http.StateActive:
    s.mu.Lock()
    delete(s.conns, conn.LocalAddr().String())
    s.mu.Unlock()
    case http.StateIdle:
    select {
    case <-ch:
    conn.Close()
    default:
    s.mu.Lock()
    s.conns[conn.LocalAddr().String()] = conn
    s.mu.Unlock()
    }
    case http.StateHijacked, http.StateClosed:
    s.waitGroup.Done()
    }
    }
    return s
    }

    // NewTLSServer returns an http.Server with better defaults configured to use
    // the certificate and private key files.
    func NewTLSServer(
    addr, cert, key string,
    handler http.Handler,
    ) (*Server, error) {
    s := NewServer(addr, handler)
    return s, s.TLS(cert, key)
    }

    // CA overrides the certificate authority on the server's TLSConfig field.
    func (s *Server) CA(ca string) error {
    certPool := x509.NewCertPool()
    buf, err := ioutil.ReadFile(ca)
    if nil != err {
    return err
    }
    certPool.AppendCertsFromPEM(buf)
    s.tlsConfig()
    s.TLSConfig.RootCAs = certPool
    return nil
    }

    // ClientCA configures the CA pool for verifying client side certificates.
    func (s *Server) ClientCA(ca string) error {
    certPool := x509.NewCertPool()
    buf, err := ioutil.ReadFile(ca)
    if nil != err {
    return err
    }
    certPool.AppendCertsFromPEM(buf)
    s.tlsConfig()
    s.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
    s.TLSConfig.ClientCAs = certPool
    return nil
    }

    // Close closes the listener the server is using and signals open connections
    // to close at their earliest convenience. Then it waits for all open
    // connections to become closed.
    func (s *Server) Close() error {
    close(s.ch)
    if err := s.listener.Close(); nil != err {
    return err
    }
    s.mu.Lock()
    for _, conn := range s.conns {
    conn.Close()
    }
    s.mu.Unlock()
    s.waitGroup.Wait()
    return nil
    }

    // ListenAndServe calls net.Listen with s.Addr and then calls s.Serve.
    func (s *Server) ListenAndServe() error {
    addr := s.Addr
    if "" == addr {
    if nil == s.TLSConfig {
    addr = ":http"
    } else {
    addr = ":https"
    }
    }
    l, err := net.Listen("tcp", addr)
    if nil != err {
    return err
    }
    if nil != s.TLSConfig {
    l = tls.NewListener(l, s.TLSConfig)
    }
    return s.Serve(l)
    }

    // ListenAndServeTLS calls s.TLS with the given certificate and private key
    // files and then calls s.ListenAndServe.
    func (s *Server) ListenAndServeTLS(cert, key string) error {
    s.TLS(cert, key)
    return s.ListenAndServe()
    }

    // Serve behaves like http.Server.Serve with the added option to stop the
    // server gracefully with the s.Close method.
    func (s *Server) Serve(l net.Listener) error {
    s.listener = l
    return s.Server.Serve(s.listener)
    }

    // TLS configures this server to be a TLS server using the given certificate
    // and private key files.
    func (s *Server) TLS(cert, key string) error {
    c, err := tls.LoadX509KeyPair(cert, key)
    if nil != err {
    return err
    }
    s.tlsConfig()
    s.TLSConfig.Certificates = []tls.Certificate{c}
    return nil
    }

    func (s *Server) tlsConfig() {
    if nil == s.TLSConfig {
    s.TLSConfig = &tls.Config{
    CipherSuites: []uint16{
    tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
    tls.TLS_RSA_WITH_RC4_128_SHA,
    },
    NextProtos: []string{"http/1.1"},
    }
    }
    }

    type serverHandler struct {
    http.Handler
    }

    func (h *serverHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // r.Header.Set("Host", r.Host) // Should I?
    r.URL.Host = r.Host
    if nil != r.TLS {
    r.URL.Scheme = "https"
    } else {
    r.URL.Scheme = "http"
    }
    h.Handler.ServeHTTP(w, r)
    }
    149 changes: 149 additions & 0 deletions server-baseline.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,149 @@
    package tigertonic

    import (
    "crypto/tls"
    "crypto/x509"
    "io/ioutil"
    "net"
    "net/http"
    )

    // Server is an http.Server with better defaults.
    type Server struct {
    http.Server
    listener net.Listener
    }

    // NewServer returns an http.Server with better defaults.
    func NewServer(addr string, handler http.Handler) *Server {
    s := &Server{
    Server: http.Server{
    Addr: addr,
    Handler: &serverHandler{
    Handler: handler,
    },
    MaxHeaderBytes: 4096,
    ReadTimeout: 60e9, // These are absolute times which must be
    WriteTimeout: 60e9, // longer than the longest {up,down}load.
    },
    }
    return s
    }

    // NewTLSServer returns an http.Server with better defaults configured to use
    // the certificate and private key files.
    func NewTLSServer(
    addr, cert, key string,
    handler http.Handler,
    ) (*Server, error) {
    s := NewServer(addr, handler)
    return s, s.TLS(cert, key)
    }

    // CA overrides the certificate authority on the server's TLSConfig field.
    func (s *Server) CA(ca string) error {
    certPool := x509.NewCertPool()
    buf, err := ioutil.ReadFile(ca)
    if nil != err {
    return err
    }
    certPool.AppendCertsFromPEM(buf)
    s.tlsConfig()
    s.TLSConfig.RootCAs = certPool
    return nil
    }

    // ClientCA configures the CA pool for verifying client side certificates.
    func (s *Server) ClientCA(ca string) error {
    certPool := x509.NewCertPool()
    buf, err := ioutil.ReadFile(ca)
    if nil != err {
    return err
    }
    certPool.AppendCertsFromPEM(buf)
    s.tlsConfig()
    s.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
    s.TLSConfig.ClientCAs = certPool
    return nil
    }

    // Close closes the listener the server is using and signals open connections
    // to close at their earliest convenience. Then it waits for all open
    // connections to become closed.
    func (s *Server) Close() error {
    return nil
    }

    // ListenAndServe calls net.Listen with s.Addr and then calls s.Serve.
    func (s *Server) ListenAndServe() error {
    addr := s.Addr
    if "" == addr {
    if nil == s.TLSConfig {
    addr = ":http"
    } else {
    addr = ":https"
    }
    }
    l, err := net.Listen("tcp", addr)
    if nil != err {
    return err
    }
    if nil != s.TLSConfig {
    l = tls.NewListener(l, s.TLSConfig)
    }
    return s.Serve(l)
    }

    // ListenAndServeTLS calls s.TLS with the given certificate and private key
    // files and then calls s.ListenAndServe.
    func (s *Server) ListenAndServeTLS(cert, key string) error {
    s.TLS(cert, key)
    return s.ListenAndServe()
    }

    // Serve behaves like http.Server.Serve with the added option to stop the
    // server gracefully with the s.Close method.
    func (s *Server) Serve(l net.Listener) error {
    s.listener = l
    return s.Server.Serve(s.listener)
    }

    // TLS configures this server to be a TLS server using the given certificate
    // and private key files.
    func (s *Server) TLS(cert, key string) error {
    c, err := tls.LoadX509KeyPair(cert, key)
    if nil != err {
    return err
    }
    s.tlsConfig()
    s.TLSConfig.Certificates = []tls.Certificate{c}
    return nil
    }

    func (s *Server) tlsConfig() {
    if nil == s.TLSConfig {
    s.TLSConfig = &tls.Config{
    CipherSuites: []uint16{
    tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
    tls.TLS_RSA_WITH_RC4_128_SHA,
    },
    NextProtos: []string{"http/1.1"},
    }
    }
    }

    type serverHandler struct {
    http.Handler
    //ch <-chan struct{}
    }

    func (h *serverHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // r.Header.Set("Host", r.Host) // Should I?
    r.URL.Host = r.Host
    if nil != r.TLS {
    r.URL.Scheme = "https"
    } else {
    r.URL.Scheme = "http"
    }
    h.Handler.ServeHTTP(w, r)
    }