Stop ListenAndServer... to ListenAndServe something else

4,489 views
Skip to first unread message

josvazg

unread,
Jun 21, 2012, 5:19:18 AM6/21/12
to golang-nuts
Let's say you are listening on "localhost:8000" and, at some point you
decide your process needs to stop doing that and instead listen on ":
443 " with TLS (on the server cert you created while listening on
"localhost:8000", for instance)

How would you close the listener?
I think there is no access to it, Am I wrong?

roger peppe

unread,
Jun 21, 2012, 5:23:54 AM6/21/12
to josvazg, golang-nuts
On 21 June 2012 10:19, josvazg <jos...@gmail.com> wrote:
> Let's say you are listening on "localhost:8000" and, at some point you
> decide your process needs to stop doing that and instead listen on ":
> 443 " with TLS (on the server cert you created while listening on
> "localhost:8000", for instance)
>
> How would you close the listener?

If you use http.Serve rather than http.ListenAndServe,
you can close the listener that you pass in.

josvazg

unread,
Jun 21, 2012, 5:49:51 AM6/21/12
to golan...@googlegroups.com, josvazg
Ok, so I should NOT use ListenAndServe unless there is no comming back from that.

Thanks!
I will rewrite my apps and the devweb launcher taking this into account.

Jose

Brad Fitzpatrick

unread,
Jun 21, 2012, 9:57:34 AM6/21/12
to josvazg, golan...@googlegroups.com
I'd start with rewriting just your Listener, not the whole app.

Tim Harig

unread,
Jun 21, 2012, 11:24:11 AM6/21/12
to golang-nuts
On Thu, Jun 21, 2012 at 02:49:51AM -0700, josvazg wrote:
> Ok, so I should NOT use ListenAndServe unless there is no comming back from
> that.

I can empathize. Maybe the documentation should be a little more explicit
about that the fact that you cannot return from ListenAndServer. I cannot
think of many situations where you do not want some graceful way to stop a
server which makes ListenAndServe useless for all but the most trivial of
applications. Yet, examples abound about creating servers in Go using
ListenAndServe. It is easy for the beginning gopher to get mislead.

Brad Fitzpatrick

unread,
Jun 21, 2012, 12:30:24 PM6/21/12
to Tim Harig, golang-nuts
I disagree.  Most my servers run forever and only stop due to some machine failure.

josvazg

unread,
Jun 21, 2012, 4:05:29 PM6/21/12
to golan...@googlegroups.com, Tim Harig
I agree with Brad. 
My use case is very particular, you usually don't need to support an special setup mode before launching the webapp.

And he was right, I didn't mean I had to rewrite my app completely, I just needed to tweak the "main loop", well not a loop to this:

l, e := net.Listen("tcp", ADDR)
if e != nil {
fmt.Println("Error:", e)
return
}
restarter.Register(l)
http.Serve(l, nil)
if restarter.SetupDone() {
fmt.Println("Reloading...")
restarter.Register(nil)
http.ListenAndServe(ADDR, nil)
}

And for the dev server called from Russ devweb this one:
if len(os.Args) != 2 || os.Args[1] != "LISTEN_STDIN" {
fmt.Fprintf(os.Stderr, "devweb slave must be invoked by devweb\n")
os.Exit(2)
}
l, err := net.FileListener(os.Stdin)
if err != nil {
log.Fatal(err)
}
l2, err := net.FileListener(os.Stdin)
if err != nil {
log.Fatal(err)
}
os.Stdin.Close()
restarter.Register(l)
http.Serve(l, nil)
if restarter.SetupDone() {
log.Println("Reloading...")
restarter.Register(nil)
http.Serve(l2, nil)
}

For the restart function code gets simplified instead:
func restart() {
if listener == nil {
fmt.Println("Already restarted")
return
}
if e := listener.Close(); e != nil {
fmt.Println("Failed to close listener: ", e)
}
http.DefaultServeMux = http.NewServeMux()
fmt.Println("Setup ends")
}

I don't think this is too bad, but if someone knows a better and simpler fix I would give it a go. ;-)

This is the full code just to test this:

Jose

Tim Harig

unread,
Jun 22, 2012, 12:29:21 AM6/22/12
to golang-nuts
On Thu, Jun 21, 2012 at 09:30:24AM -0700, Brad Fitzpatrick wrote:
> On Thu, Jun 21, 2012 at 8:24 AM, Tim Harig <timh...@gmx.com> wrote:
>
> > On Thu, Jun 21, 2012 at 02:49:51AM -0700, josvazg wrote:
> > > Ok, so I should NOT use ListenAndServe unless there is no comming back
> > from
> > > that.
> >
> > I can empathize. Maybe the documentation should be a little more explicit
> > about that the fact that you cannot return from ListenAndServe. I cannot
> > think of many situations where you do not want some graceful way to stop a
> > server which makes ListenAndServe useless for all but the most trivial of
> > applications. Yet, examples abound about creating servers in Go using
> > ListenAndServe. It is easy for the beginning gopher to get mislead.
> >
>
> I disagree. Most my servers run forever and only stop due to some machine
> failure.

I am glad that you can do that. Most servers that I work with need
to be taken down for one reason or another whether for software or
hardware upgrades. Sometimes, it is better to do so gracefully rather
then using the default SIGTERM handler which may drop connections and/or
leave anything that was being written in an inconsistent state.

The present development situation also means that a lot of servers may
not be traditional production servers. Since Go yet lacks a viable GUI
package that works across platforms, the default answer seems to be to
create a web application hosted on a local server. I see no reason why
these servers need to run forever after they have been started.

Furthermore, this isn't a problem with the package itself since a graceful
shutdown can be achieved by manually handing the listener. The
problem is that most examples you find for writing a server in Go point
new users to boilerplate code using ListenAndServe. They may not even
consider the problem of shutdown until that they have started development.
I just feel that it is wise to place some kind of warning to prevent people
from going down that rabbit hole blindly. Doing so doesn't affect *your*
ability to use ListenAndServe if that is really what you want to do. Such
a warning would have prevented this post and others like it that I have
seen at stackoverflow and other places.

Brad Fitzpatrick

unread,
Jun 22, 2012, 12:27:11 PM6/22/12
to Tim Harig, golang-nuts
You make it sounds like picking the wrong path between ListenAndServe versus Listen+Server independently is some disastrous mistake, dooming yourself into rewriting your entire application if you get it wrong.  You just change a few lines.  All the Handlers are unchanged.

FWIW, I have written servers that shut down cleanly and go into draining mode where they stop accepting new connections but finish their existing ones.  I didn't find it painful with Go, though.

josvazg

unread,
Jun 26, 2012, 3:57:20 AM6/26/12
to golan...@googlegroups.com, Tim Harig
Just a quick note to say I finally DID NOT restart the app, not even a soft restart.

Instead, inspired by this thread, I realized I didn't really want to do that, what I wanted was to start the main webapp listener while leaving the setup listener alive just to redirect people that might have got stuck in the setup process to proceed to the normal webapp AFTER downloading the proper certificate and read some simple instructions. 
(It can happen that several people may be trying to setup the same app at once BUT only one can succeed. It's highly improbable to have several users setting up the app, moreover when I forced the setup webapp to be "localhost" only, but I have to deal with that cause this is a web interface, for better and worse!)

As http.ServeMux doesn't allow me to change mappings, instead I created a flexible mapping like this:
// rootFunc points to the root function that is different on setup and normal mode
var rootFunc func(w http.ResponseWriter, r *http.Request)
...
http.HandleFunc("/", smartSwitch)
...
// smartSwitch redirects to showSetup or index depending on whether the setup is done or not
func smartSwitch(w http.ResponseWriter, r *http.Request) { 
    rootFunc(w,r)
}

And when the app state changes I just do:
oneSetup.Lock()
defer oneSetup.Unlock()
...
if !setupDone {
...
   setupDone = true  // state change
   rootFunc=restart  // rootRunc now does something else from now on
   http.DefaultServeMux=http.NewServeMux()  // need a new default ServeMux for the new server
   go WebCA() // "second" start will start only the normal app ListenAndServe
Reply all
Reply to author
Forward
0 new messages