Giter Site home page Giter Site logo

graceful's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graceful's Issues

Waiting after SIGINT even though no connection is open

Hi,

I am using graceful with Negroni:

package main

import (
    // Stdlib
    "flag"
    "time"

    // Vendor
    "github.com/codegangsta/negroni"
    "gopkg.in/tylerb/graceful.v1"
)

func main() {
    flagAddress := flag.String("addr", ":3000", "network address")

    n := negroni.Classic()
    graceful.Run(*flagAddress, 10*time.Second, n)
}

It just serves index.html from the public folder.

Now, for some reason, when I send SIGINT to the server, it waits for 10 seconds even though there is no active connection. Is that the desired behaviour? I was thinking that it would just exit when there are no pending requests...

$ make run
[negroni] Started GET /
[negroni] Completed 200 OK in 36.588592ms
[negroni] Started GET /favicon.ico
[negroni] Completed 0  in 33.599µs
^C
(10 seconds)
$

Using https and setting timeout to 0 does not shutdown the server when there are no more requests

I am using the following code:

package main

import (
    "crypto/tls"
    "fmt"
    "gopkg.in/tylerb/graceful.v1"
    "net/http"
    "time"
)

func main() {

    cert := []byte("-----BEGIN CERTIFICATE-----...")

    key := []byte("-----BEGIN RSA PRIVATE KEY-----...")

    mux := http.NewServeMux()

    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Welcome to the home page!")
    })

    xcert, err := tls.X509KeyPair(cert, key)

    if err != nil {
        panic(err)
    }

    tlsConf := &tls.Config{
        Certificates: []tls.Certificate{xcert},
    }

    tlsConf.BuildNameToCertificate()

    srv := &graceful.Server{
        Timeout: 0,

        Logger: graceful.DefaultLogger(),

        Server: &http.Server{
            Addr:      ":https",
            Handler:   mux,
            TLSConfig: tlsConf,
        },
    }

    srv.ListenAndServeTLSConfig(tlsConf)
}

I am using go 1.6 and Chrome 50 (latest) on Windows 10 64-bit.

Here are the steps to reproduce:

  1. Create the self signed certificates and build.
  2. Start the server.
  3. Access it from Chrome or another browser: https://localhost.
  4. Shutdown the server (ctrl-c).
  5. See that the sever never exits.

Non-http servers seem to correctly exit after sometime if timeout is set to 0.

BeforeShutdown callback?

Would you be open to a BeforeShutdown callback? ie:

type Server struct {
    ...

    // BeforeShutdown is called before the listener is closed.
    BeforeShutdown func()

    ...
}

...

func (srv *Server) handleInterrupt(interrupt chan os.Signal, listener net.Listener) {
    <-interrupt

    if srv.BeforeShutdown != nil {
        srv.BeforeShutdown()
    }

    srv.SetKeepAlivesEnabled(false)
    _ = listener.Close() // we are shutting down anyway. ignore error.

    if srv.ShutdownInitiated != nil {
        srv.ShutdownInitiated()
    }

    srv.stopLock.Lock()
    signal.Stop(interrupt)
    close(interrupt)
    srv.interrupt = nil
    srv.stopLock.Unlock()
}

I can write a pull request in a few minutes, alternatively if you just added it too :)

This would allow some round-trip communication for servers that have a two-way connection.

FD Leak under Load

Hi,
Having trouble finding a bug that's causing a File Descriptor leak with a bittorrent tracker based on graceful:
https://github.com/chihaya/chihaya/blob/develop/http/http.go#L113
(I'm working in the develop branch)

I can't pin-point whats causing it but something is causing the daemon to at random intervals keep eating FD's until it hits a system limit, then fails health checks, thus getting restarted then working again until the bug is hit again. We tried setting a ReadTimeout and WriteTimeout (http://pastebin.com/zZLmqBYN) but this did not help.

Please let me know what other information that could help. Would love any pointer of things to try. Thankyou in advance.

Data race on graceful shutdown via SIGTERM

I start the server with ListenAndServe as follows:

server := &graceful.Server{
    Timeout: 10 * time.Second,
    Server: &http.Server{
        Addr:        fmt.Sprintf("%s:%d", s.ListenIp, s.ListenPort),
        ReadTimeout: time.Duration(5) * time.Second,
        Handler:     s,
    },
}

err := server.listenAndServe()

When I terminate the process with SIGTERM sent via ^C I get the following race:

==================
WARNING: DATA RACE
Read by main goroutine:
  github.com/stretchr/graceful.(*Server).Serve()
      /home/dcelasun/go/src/github.com/stretchr/graceful/graceful.go:249 +0xa6c
  github.com/stretchr/graceful.(*Server).ListenAndServe()
      /home/dcelasun/go/src/github.com/stretchr/graceful/graceful.go:110 +0x2ad
  github.com/redacted/server.(*Server).Start()
      /home/dcelasun/go/src/github.com/redacted/server/server.go:88 +0x7c8
  main.main()
      /home/dcelasun/go/src/github.com/redacted/main.go:72 +0xd92

Previous write by goroutine 13:
  github.com/stretchr/graceful.func·004()
      /home/dcelasun/go/src/github.com/stretchr/graceful/graceful.go:273 +0x9d
  sync.(*Once).Do()
      /build/go/src/go-1.4.2/src/sync/once.go:44 +0xe6
  github.com/stretchr/graceful.(*Server).StopChan()
      /home/dcelasun/go/src/github.com/stretchr/graceful/graceful.go:275 +0x84
  github.com/redacted/server.(*Server).Shutdown()
      /home/dcelasun/go/src/github.com/redacted/server/server.go:192 +0x35

Goroutine 13 (running) created at:
  github.com/redacted/server.(*Server).Start()
      /home/dcelasun/go/src/github.com/redacted/server/server.go:85 +0x777
  main.main()
      /home/dcelasun/go/src/github.com/redacted/main.go:72 +0xd92
==================

I'm blocking on theStopChan channel in a separate goroutine:

go s.Shutdown(server)

func (s *Server) Shutdown(server *graceful.Server) {
    <-server.StopChan()
    s.Close() // Closes other stuff, not relevant to this bug
}

http2_test.go:52: Expected HTTP/2 connection to server, but connection was using HTTP/1.1

The test suite fails here using Go 1.7 on Debian unstable amd64:

export GOPATH=$(mktemp -d)
go get -v github.com/tylerb/graceful
go get -v golang.org/x/net/http2
go test -v github.com/tylerb/graceful
=== RUN   TestGracefulRun
--- PASS: TestGracefulRun (0.25s)
=== RUN   TestGracefulRunLimitKeepAliveListener
--- PASS: TestGracefulRunLimitKeepAliveListener (0.25s)
=== RUN   TestGracefulRunTimesOut
--- PASS: TestGracefulRunTimesOut (0.60s)
=== RUN   TestGracefulRunDoesntTimeOut
--- PASS: TestGracefulRunDoesntTimeOut (1.00s)
=== RUN   TestGracefulRunDoesntTimeOutAfterConnectionCreated
--- PASS: TestGracefulRunDoesntTimeOutAfterConnectionCreated (1.10s)
=== RUN   TestGracefulRunNoRequests
--- PASS: TestGracefulRunNoRequests (0.00s)
=== RUN   TestGracefulForwardsConnState
--- PASS: TestGracefulForwardsConnState (0.25s)
=== RUN   TestGracefulExplicitStop
--- PASS: TestGracefulExplicitStop (0.10s)
=== RUN   TestGracefulExplicitStopOverride
--- PASS: TestGracefulExplicitStopOverride (0.10s)
=== RUN   TestBeforeShutdownAndShutdownInitiatedCallbacks
--- PASS: TestBeforeShutdownAndShutdownInitiatedCallbacks (0.10s)
=== RUN   TestBeforeShutdownCanceled
--- PASS: TestBeforeShutdownCanceled (0.60s)
=== RUN   TestNotifyClosed
--- PASS: TestNotifyClosed (0.01s)
=== RUN   TestStopDeadlock
--- PASS: TestStopDeadlock (0.10s)
=== RUN   TestStopRace
--- PASS: TestStopRace (0.00s)
=== RUN   TestInterruptLog
--- PASS: TestInterruptLog (0.00s)
=== RUN   TestMultiInterrupts
--- PASS: TestMultiInterrupts (0.00s)
=== RUN   TestLogFunc
--- PASS: TestLogFunc (0.00s)
=== RUN   TestHTTP2ListenAndServeTLS
--- FAIL: TestHTTP2ListenAndServeTLS (0.14s)
    http2_test.go:52: Expected HTTP/2 connection to server, but connection was using HTTP/1.1
=== RUN   TestHTTP2ListenAndServeTLSConfig
--- FAIL: TestHTTP2ListenAndServeTLSConfig (0.11s)
    http2_test.go:52: Expected HTTP/2 connection to server, but connection was using HTTP/1.1
FAIL
exit status 1
FAIL    github.com/tylerb/graceful  4.730s

Ensure 100% test coverage

The codebase is so small that it should be possible to ensure 100% test coverage. Currently it is at 93.5%, but that is prior to the incoming PR #14.

go routine and memory leak

every time a http connection opens or closes a new count is sent inside a go routine to the active channel. that means for every connection in the lifetime of the server these go routine stacks (n_2_8 kb) are soon enough to kill the server doing exactly the opposite of what is advertised:

to better understand

  1. connection events are sent to the unbuffered add and remove channels https://github.com/stretchr/graceful/blob/245127cc6e691693f813804de1fdebc67ff6044f/graceful.go#L40
  2. these events are reflected in the connection map exclusive (almost) to one go routine https://github.com/stretchr/graceful/blob/245127cc6e691693f813804de1fdebc67ff6044f/graceful.go#L51
  3. then a new go routine is spun up for the sole purpose of sending to a unbuffered channel... https://github.com/stretchr/graceful/blob/245127cc6e691693f813804de1fdebc67ff6044f/graceful.go#L53
  4. ... that is first read when the server received the kill or interrupt signal https://github.com/stretchr/graceful/blob/245127cc6e691693f813804de1fdebc67ff6044f/graceful.go#L82
  5. building up a massive amount of parked go-routines

you can test this with a simple server as from the readme
$ go run ./server.go
$ for ((x=0; x<=500; x++)) do curl -s --no-keepalive http://localhost:3001 > /dev/null ; done
then send a sigquit signal (Ctrl-) to the terminal running the server and see about 2000 go routines using up 8kb (4kb soon again) each

Allow 0 timeout

0 timeout would never time out, allowing all active requests to complete.

Should we use gopkg.in for releases?

I'd like to discuss solidifying v1 API and releasing it using gopkg.in. This would require us to be backward compatible through major releases. I want to get much better about proper releases and compatibility through versions. gopkg.in facilitates that, as well as making consistent, reproducible builds possible.

@mb0 any thoughts on this?

ListenAndServeTLS does not have a signature that operates on a server struct

Already opened a pull request for this, my apologies, I didn't see the contribution instructions.

In the project I am working on, I was using a helper function to create and configure a server object, which was then used in calling the listen and serve functions, similar to the example in the documentation:

mux := // ...
srv := &graceful.Server{
  Timeout: 10 * time.Second,

  Server: &http.Server{
    Addr: ":1234",
    Handler: mux,
  },
}

srv.ListenAndServe()

This issue I encountered is that there was no matching signature for ListenAndServeTLS - one that operated on a server object. There was only the static function that took a server as a parameter.

This made the code less consistent, and seemed like it should be in the library, so I refactored the functions to add an instance-specific one, as well as modified the static function to call it, similar to the ListenAndServe implementation, providing backwards compatibility.

TestNotifyClosed: bind: address already in use

The test suite frequently fails using v1.2.4 with Go 1.6 on Linux x86_64 with the following output:

go test -v gopkg.in/tylerb/graceful.v1
=== RUN   TestGracefulRun
--- PASS: TestGracefulRun (1.25s)
=== RUN   TestGracefulRunTimesOut
--- PASS: TestGracefulRunTimesOut (2.60s)
=== RUN   TestGracefulRunDoesntTimeOut
--- PASS: TestGracefulRunDoesntTimeOut (2.00s)
=== RUN   TestGracefulRunNoRequests
--- PASS: TestGracefulRunNoRequests (1.00s)
=== RUN   TestGracefulForwardsConnState
--- PASS: TestGracefulForwardsConnState (2.25s)
=== RUN   TestGracefulExplicitStop
--- PASS: TestGracefulExplicitStop (1.10s)
=== RUN   TestGracefulExplicitStopOverride
--- PASS: TestGracefulExplicitStopOverride (1.10s)
=== RUN   TestBeforeShutdownAndShutdownInitiatedCallbacks
--- PASS: TestBeforeShutdownAndShutdownInitiatedCallbacks (1.10s)
=== RUN   TestNotifyClosed
--- FAIL: TestNotifyClosed (0.00s)
        graceful_test.go:362: listen tcp :9654: bind: address already in use
=== RUN   TestStopDeadlock
--- PASS: TestStopDeadlock (1.00s)
=== RUN   TestStopRace
--- PASS: TestStopRace (1.00s)
FAIL
exit status 1
FAIL    gopkg.in/tylerb/graceful.v1     14.415s

Doesn't serve http/2

I have a simple https webserver like so:

package main

import (
    //"gopkg.in/tylerb/graceful.v1"
    "net/http"
)

func main() {

    srv := &http.Server{Addr: ":https"}
    srv.ListenAndServeTLS("cert.crt", "key.pem")
}

If I build it and run in and then access it via Chrome, I can see that it uses http/2 (h2) from the dev tools.

If I add graceful into the mix, it only uses http/1.1:

package main

import (
    "gopkg.in/tylerb/graceful.v1"
    "net/http"
    "time"
)

func main() {

    srv := &http.Server{Addr: ":https"}
    graceful.ListenAndServeTLS(srv, "cert.crt", "key.pem", 10*time.Second)
}

Handle SIGTERM as well?

A SIGTERM is, along with SIGINT, a fairly standard way to signal
to processes that they should gracefully shut down:

SIGTERM is often used in programatic cases (e.g. by Upstart and
Heroku to signal graceful shutdown) and SIGINT in interactive cases
(e.g. CTR-C in a terminal to stop a process). It would therefore be
useful if graceful handled both signals, instead of just the current
SIGINT.

What do you think about adding that SIGTERM support ? I'd be
willing to write a code+docs patch if you were interested.

Thanks for writing and releasing graceful!

TestGracefulRun: No response when a response was expected

The test suite sporadically fails using v1.2.4 with Go 1.6 on Linux x86_64 with the following output:

=== RUN   TestGracefulRun
--- FAIL: TestGracefulRun (1.25s)
        graceful_test.go:44: No response when a response was expected.
=== RUN   TestGracefulRunTimesOut
--- PASS: TestGracefulRunTimesOut (2.60s)
=== RUN   TestGracefulRunDoesntTimeOut
--- PASS: TestGracefulRunDoesntTimeOut (2.00s)
=== RUN   TestGracefulRunNoRequests
--- PASS: TestGracefulRunNoRequests (1.00s)
=== RUN   TestGracefulForwardsConnState
--- PASS: TestGracefulForwardsConnState (2.25s)
=== RUN   TestGracefulExplicitStop
--- PASS: TestGracefulExplicitStop (1.10s)
=== RUN   TestGracefulExplicitStopOverride
--- PASS: TestGracefulExplicitStopOverride (1.10s)
=== RUN   TestBeforeShutdownAndShutdownInitiatedCallbacks
--- PASS: TestBeforeShutdownAndShutdownInitiatedCallbacks (1.10s)
=== RUN   TestNotifyClosed
--- PASS: TestNotifyClosed (0.00s)
=== RUN   TestStopDeadlock
--- PASS: TestStopDeadlock (1.00s)
=== RUN   TestStopRace
--- PASS: TestStopRace (1.00s)
FAIL
exit status 1
FAIL    gopkg.in/tylerb/graceful.v1     14.419s

graceful not working for hijacked (websocket) connections

I just discovered that unfortunately graceful does not work for my use case.
I want it to wait for long lived websocket connections. But those connections seem to be discounted as soon as they are hijacked. So they don't really count as active connections at all.
I'm not really familiar to the internal workings of the http servers connection handling but looking at the code and specifically at 33d2033 it seems to me there is no easy way of making graceful work for highjacked connections. Is this correct or is there a way to make graceful wait for all connections the http server?

can't customize signal handling

The current implementation hard codes the signal handling that the graceful shutdown reacts to. In my use case, I want to be able to stop the server with and without signal handling. This is especially useful for tests.

cannot use go get

I'm running go get gopkg.in/tylerb/graceful.v1 and encountering the following errors in the terminal:

../../tylerb/graceful/graceful.go:41: undefined: http.ConnState
../../tylerb/graceful/graceful.go:169: srv.Server.ConnState undefined (type *http.Server has no field or method ConnState)
../../tylerb/graceful/graceful.go:169: undefined: http.ConnState
../../tylerb/graceful/graceful.go:171: undefined: http.StateNew
../../tylerb/graceful/graceful.go:173: undefined: http.StateClosed
../../tylerb/graceful/graceful.go:173: undefined: http.StateHijacked
../../tylerb/graceful/graceful.go:271: srv.SetKeepAlivesEnabled undefined (type *Server has no field or method SetKeepAlivesEnabled)

Anything I'm doing wrong?

How to disable http2

Since this commit 6dd9b0d http2 is enabled by default with Go 1.7.1.
How can I disabled it, cause MS Edge have SCRIPT7002: XMLHttpRequest: Network Error 0x2eff.

Fix race condition

The os.Signal channel is global. This causes a race condition when multiple servers are run at the same time.

Using ListenLimit and TCPKeepAlive panics

// main.go

package main

import (
    "log"
    "net/http"
    "time"

    "gopkg.in/tylerb/graceful.v1"
)

func main() {
    srv := &graceful.Server{
        ListenLimit:  10,
        TCPKeepAlive: 1 * time.Minute,
        Server: &http.Server{
            Handler: http.NewServeMux(),
            Addr:    ":8080",
        },
    }
    log.Println(srv.ListenAndServe())
}
$ go run main.go
panic: interface conversion: net.Listener is *netutil.limitListener, not *net.TCPListener

goroutine 1 [running]:
panic(0x3184e0, 0xc82000e500)
    /usr/local/Cellar/go/1.6.2/libexec/src/runtime/panic.go:481 +0x3e6
github.com/djui/mb-go/vendor/gopkg.in/tylerb/graceful%2ev1.(*Server).Serve(0xc820080780, 0x1245640, 0xc8200b6760, 0x0, 0x0)
    /Users/abc/dev/go/src/github.com/djui/mb-go/vendor/gopkg.in/tylerb/graceful.v1/graceful.go:242 +0x81f
github.com/djui/mb-go/vendor/gopkg.in/tylerb/graceful%2ev1.(*Server).ListenAndServe(0xc820080780, 0x0, 0x0)
    /Users/abc/dev/go/src/github.com/djui/mb-go/vendor/gopkg.in/tylerb/graceful.v1/graceful.go:151 +0xdf
main.main()
    /Users/abc/dev/go/src/github.com/djui/mb-go/foo.go:20 +0x1a7
exit status 2

Building the next major version of graceful

@tylerb mentioned that it is probably a good time to build the next version of graceful with all the lessons learnt while maintaining the library and using it in production. This might be a complete rewrite, or we could refactor it to the next version. We are now interested in what the community would like to see in the next version as well as any feedback you may have.

For starters, these are what we care about the most:

  1. Simple, clean, concise public API.
  2. Works with any server, like it does now.
  3. Supports HTTP 2
  4. Robust, correct, clean tests.
  5. NOT supporting zero-downtime restarts, there are other packages for that.

"use of closed network connection" error returned by Serve()

I'm just trying this out and I noticed that ListenAndServe and ListenAndServeTLS always return an error for me when shutting down gracefully.
The error I'm seeing is:

accept tcp 127.0.0.1:1234: use of closed network connection

Thats a bit unfortunate, normally an error returned by ListenAndServe indicates that there was a problem binding the listening socket.
Is this a known problem or is there something wrong with my setup? I'm on OSX, go 1.5.2.

Example code to reproduce:

import (
    "fmt"
    "net/http"
    "os"

    "gopkg.in/tylerb/graceful.v1"
)

func main() {

    srv := &graceful.Server{
        Server: &http.Server{
            Addr: ":1234",
            Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("hello world"))
            }),
        },
    }

    if err := srv.ListenAndServe(); err != nil {
        fmt.Println("error: ", err)
        os.Exit(1)
    }
}

data race on connections map

there is concurrent write and read access possible on timeout condition.

please run go test -race to reproduce

"go get" throwing an error

I am getting following error when I try to get this package using go get

go get github.com/stretchr/graceful

github.com/stretchr/graceful

../github.com/stretchr/graceful/graceful.go:38: server.ConnState undefined (type *http.Server has no field or method ConnState)
../github.com/stretchr/graceful/graceful.go:38: undefined: http.ConnState
../github.com/stretchr/graceful/graceful.go:40: undefined: http.StateActive
../github.com/stretchr/graceful/graceful.go:42: undefined: http.StateClosed
../github.com/stretchr/graceful/graceful.go:42: undefined: http.StateIdle
../github.com/stretchr/graceful/graceful.go:77: server.SetKeepAlivesEnabled undefined (type *http.Server has no field or method SetKeepAlivesEnabled)

check for StateIdle?

Looks like graceful doesn't track connections going to StateIdle and waits for them to completely timeout. This means servers with very long idle timeouts always have to wait for their connections to timeout or set a more aggressive timeout for the graceful shutdown. The more aggressive timeout is not quite what we want since that could shut down the server while there are requests still in flight.

It would be nice for graceful to track idle connection states, close them when shutdown is initiated, and not count them against the server being able to close.

Close Listener instead of 503

We should close the listening socket, refusing all further requests to the server. This will allow another server to spin up and listen on that same port while the in-flight requests on the first instance finish processing.

panic when server.Stop()

the stopLock and chanLock a private member. but it does not initialized any where.

when I call server.Stop(), server.stopLock is still a empty value.

srv.stopLock.Lock() panic.

depends on go 1.5+?

I'm using go 1.4 and graceful no longer works.

# golang.org/x/net/http2
/Users/matthew/go/src/golang.org/x/net/http2/transport.go:213: req.Cancel undefined (type *http.Request has no field or method Cancel)
/Users/matthew/go/src/golang.org/x/net/http2/transport.go:217: req.Cancel undefined (type *http.Request has no field or method Cancel)
/Users/matthew/go/src/golang.org/x/net/http2/transport.go:776: req.Cancel undefined (type *http.Request has no field or method Cancel)

Pre-privilege dropping listening

I have an application in which an HTTP server binds to privileged ports, then drops privileges and starts serving. In order to do this, I have to create my own net.Listener. This is rather cumbersome and doesn't take advantage of connection limiting.

Calling .ListenAndServe() before privilege dropping creates a race condition, a brief window of time where HTTP requests could be served by a non-privilege-dropped connection.

What would be nice is some sort of .PreListen() method which opens the listener but does not serve. If .ListenAndServe() is then called, it uses the existing listener if .PreListen() was called.

If I wrote this code, would you be interested in merging it?

Closing previously idle connection early

Hi, finding odd behavior here and think it's a bug. On Centos6, go version go1.6.2 linux/amd64. Sample program at bottom. Basically all this does is create the server with a handler that artificially takes 2s to process, with a graceful timeout of 5s.

Then it sends a synchronous request, reads the response (so no shutdown involved yet), then another in a goroutine and calls Stop().

Expected behavior: with a 5s Timeout on graceful.Server, it should let the running request (which should take 2s) finish successfully.
Expected output:
Error (after 2.005738855s): <nil>

Actual behavior: though the program takes 2s to actually quit, the request terminated immediately.
Actual output:
Error (after 102.433484ms): Get http://localhost:3001/: dial tcp [::1]:3001: getsockopt: connection refused

Note that the first synchronous request makes a difference. If you comment that out, then it behavior as expected. I suspect this has something to do with srv.Server.ConnState not handling StateActive.

package main

import (
    "fmt"
    "io/ioutil"
    "net"
    "net/http"
    "sync"
    "time"

    "github.com/tylerb/graceful"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        time.Sleep(2 * time.Second)
        fmt.Fprintf(w, "done")
    })

    s := &graceful.Server{
        Server: &http.Server{
            Handler: mux,
        },
        Timeout: 5 * time.Second,
    }

    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()

        lp, err := net.Listen("tcp", ":3001")
        if err != nil {
            panic(fmt.Sprintf("Failed to listen: %v", err))
        }
        s.Serve(lp)
    }()

    // Ensure we are listening on the port
    time.Sleep(100 * time.Millisecond)

    // Make a sample first request. The connection will be left idle.
    resp, err := http.Get("http://localhost:3001/")
    if err != nil {
        panic(fmt.Sprintf("first request failed: %v", err))
    }
    ioutil.ReadAll(resp.Body)
    resp.Body.Close()

    start := time.Now()
    wg.Add(1)
    go func() {
        defer wg.Done()

        _, err := http.Get("http://localhost:3001/")
        fmt.Printf("Error (after %v): %v\n", time.Now().Sub(start), err)
    }()

    // Ensure the request goes out
    time.Sleep(100 * time.Millisecond)

    s.Stop(5 * time.Second)

    wg.Wait()
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.