Stop HTTP Server with Context cancel

2,303 views
Skip to first unread message

Pierre Durand

unread,
Apr 4, 2017, 2:02:16 PM4/4/17
to golang-nuts
Hello

I wrote a small helper to stop an HTTP Server when a Context is canceled.

What do you think ?
Is it OK to use context cancellation for stopping long running functions ?

Johnny Luo

unread,
Apr 5, 2017, 1:13:19 AM4/5/17
to golang-nuts
func listenAndServe(ctx context.Context, srv *http.Server, errCh chan<- error) {
 err
:= srv.ListenAndServe()
 
select {
 
case errCh <- err:
 
case <-ctx.Done():
 
}
}
ListenAndServe is blocking function, so the select will not happen until ListenAndServe return.  and errCh become no use

Pierre Durand

unread,
Apr 6, 2017, 4:29:58 AM4/6/17
to golang-nuts
I did it for a good reason:
If the context is canceled `srv.Shutdown` is called.
Then, `<-errCh` is not called anymore.
This code ensures that there is not leaking goroutine.

Kemal Hadimli

unread,
Apr 6, 2017, 5:04:29 AM4/6/17
to golang-nuts
Isn't the select processing order random? IIRC the only guarantee is "default" case is handled as a low priority.

So, something like this maybe?

select {
case errCh <- err:
default:
select {
case <-ctx.Done():
}
}

Again, take this with a grain of salt. I didn't check the spec or code, just off the top of my head.


Best,

Pierre Durand

unread,
Apr 6, 2017, 5:15:23 AM4/6/17
to golang-nuts
Yes you're right, the processing order is pseudo random.

But my code also handle another edge case:
If `srv.ListenAndServe()` returns an error BEFORE we reach the code `case err := <-errCh:`,
with `default` the error is ignored.

My code ensure that:
- there is no leaking goroutine
- returned error is not ignored

Konstantin Khomoutov

unread,
Apr 6, 2017, 5:19:53 AM4/6/17
to Kemal Hadimli, golang-nuts
On Thu, 6 Apr 2017 02:04:29 -0700 (PDT)
Kemal Hadimli <dis...@gmail.com> wrote:

> Isn't the select processing order random? IIRC the only guarantee is
> "default" case is handled as a low priority.
>
> So, something like this maybe?
>
> select {
> case errCh <- err:
> default:
> select {
> case <-ctx.Done():
> }
> }
>
> Again, take this with a grain of salt. I didn't check the spec or
> code, just off the top of my head.

The default branch proceeds only if none of the case branches is ready
for communication so your reasoning appears to be correct.

winlin

unread,
May 17, 2017, 12:54:04 AM5/17/17
to golang-nuts
Will this be OK?

func ListenAndServe(ctx context.Context, srv *http.Server) error {
ctx,cancel := context.WitchCancel(ctx)

wg := sync.WaitGroup{}
defer wg.Wait()

wg.Add(1)
go func(ctx context.Context) {
defer cancel()
                err := srv.ListenAndServe()
fmt.Println("Server err is", err)
}(ctx)

select {
case <-ctx.Done():
srv.Close()
}

return ctx.Err()
}

When the http server error and quit, it will call the cancel to unblock the select.
When the ctx is cancelled, it will call svr.Close() to unblock the server.
Does it works?

mhh...@gmail.com

unread,
May 17, 2017, 5:40:56 AM5/17/17
to golang-nuts
> Is it OK to use context cancellation for stopping long running functions ?

yes, i d say so.

About contexts,
https://www.youtube.com/watch?v=LSzR0VEraWw
https://www.youtube.com/watch?v=8M90t0KvEDY

From scratch,
with some mocks to test&try.

package main

import (
   
"context"
   
"log"
   
"time"
)

func main
() {
    ctx
, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
    defer cancel
()
   
var err error
    listenSig
:= make(chan error)
    go func
() { listenSig <- <-listenAndServe() }()//IRL, not sure if that will return if server has shutdown.
// this is unclear at https://beta.golang.org/src/net/http/server.go?s=79791:79837#L2615
// i d say its all about timeouts.
   
select {
   
case err = <-listenSig:
        log
.Println("server soft end")

   
case <-ctx.Done():
        log
.Println("context ended")

        log
.Println("server hard stop")
       
if err = shutdown( /*context.Background()*/ ); err == nil {
            err
= ctx.Err()
       
}
        log
.Println("server ended")
   
}
    log
.Println(err)

}

func listenAndServe
() chan error {
    e
:= make(chan error)
    go func
() {
       
<-time.After(time.Second)
        e
<- nil
   
}()
   
return e
}

func shutdown
() error {
   
<-time.After(2 * time.Second)
   
return nil
}


that being said, server.Shutdown seems blocking,
https://beta.golang.org/pkg/net/http/#Server.Shutdown
> Shutdown ... waiting indefinitely for connections to return to idle and then shut down.
https://beta.golang.org/src/net/http/server.go?s=74304:74358#L2440

If so, d go func that one and put its err to a sink logger.
It will hang there until all clients are out, so the server must be configured with timeouts.
Or use a context with deadline ?
I m not sure what s the best, maybe both are needed ?
It looks likes this ctx parameter is sugar only.

Apart, does server.Shutdown allows a straight restart ?
+/- like if i d use socket_reuse port/addr?


> This code ensures that there is not leaking goroutine.

Reply all
Reply to author
Forward
0 new messages