start http server in goroutine

5,898 views
Skip to first unread message

Mikhail Vitsen

unread,
Nov 9, 2014, 7:33:08 PM11/9/14
to golan...@googlegroups.com
Hello everyone!
I try to write simple service, and i want to start http server in separate goroutine, not in main "thread". I know that every http request processes in separate goroutine, but my server should serve not only http, but tcp too - that's why i want to start http server asynchronously.


If i remove "go" in 9th line (go server.ListenAndServe()) - everything works fine, but main thread blocks.
If i doesn't remove "go" - i can't receive response from server at all.

What is the problem?


Dave Cheney

unread,
Nov 9, 2014, 7:52:58 PM11/9/14
to golan...@googlegroups.com
When main() exits, your program will exit. If you start some work in another goroutine, you have to arrange that main is blocked until that work is complete.

Mikhail Vitsen

unread,
Nov 10, 2014, 7:30:35 AM11/10/14
to golan...@googlegroups.com
Of course, i know it. main() has infinite loop. After starting goroutine for http server, main() starts anouther goroutine for tcp socket and then processes messages from both goroutines.


понедельник, 10 ноября 2014 г., 3:52:58 UTC+3 пользователь Dave Cheney написал:

Konstantin Khomoutov

unread,
Nov 10, 2014, 7:49:13 AM11/10/14
to Mikhail Vitsen, golan...@googlegroups.com
On Mon, 10 Nov 2014 04:30:35 -0800 (PST)
Mikhail Vitsen <mikhai...@gmail.com> wrote:

[...]
> >> http://play.golang.org/p/h0zoMJVybU
> >>
> >> If i remove "go" in 9th line (go server.ListenAndServe()) -
> >> everything works fine, but main thread blocks.
> >> If i doesn't remove "go" - i can't receive response from server at
> >> all.
> >>
> >> What is the problem?
> >>
> > When main() exits, your program will exit. If you start some work
> > in another goroutine, you have to arrange that main is blocked
> > until that work is complete.

> Of course, i know it. main() has infinite loop. After starting
> goroutine for http server, main() starts anouther goroutine for tcp
> socket and then processes messages from both goroutines.

The main() in the sample code you've posted does not has an infinite
loop: it agganges for HTTP serving to start and then quits.

Wrong playground link?

Mikhail Vitsen

unread,
Nov 10, 2014, 8:28:13 AM11/10/14
to golan...@googlegroups.com, mikhai...@gmail.com
As soon as it is impossible to run server code in playground - this link is only an example (i thought it is good way to show go code on playground) and represents only the idea of a problem.
i've fixed it http://play.golang.org/p/boBae29AB9



понедельник, 10 ноября 2014 г., 15:49:13 UTC+3 пользователь Konstantin Khomoutov написал:

Konstantin Khomoutov

unread,
Nov 10, 2014, 10:09:02 AM11/10/14
to Mikhail Vitsen, golan...@googlegroups.com
On Mon, 10 Nov 2014 05:28:12 -0800 (PST)
Mikhail Vitsen <mikhai...@gmail.com> wrote:

> > > >> http://play.golang.org/p/h0zoMJVybU
> > > >>
> > > >> If i remove "go" in 9th line (go server.ListenAndServe()) -
> > > >> everything works fine, but main thread blocks.
> > > >> If i doesn't remove "go" - i can't receive response from
> > > >> server at all.
> > > >>
> > > >> What is the problem?
> > > >>
> > > > When main() exits, your program will exit. If you start some
> > > > work in another goroutine, you have to arrange that main is
> > > > blocked until that work is complete.
> >
> > > Of course, i know it. main() has infinite loop. After starting
> > > goroutine for http server, main() starts anouther goroutine for
> > > tcp socket and then processes messages from both goroutines.
> >
> > The main() in the sample code you've posted does not has an
> > infinite loop: it agganges for HTTP serving to start and then
> > quits.
> >
> > Wrong playground link?
> >
> As soon as it is impossible to run server code in playground - this
> link is only an example (i thought it is good way to show go code on
> playground) and represents only the idea of a problem.
> i've fixed it http://play.golang.org/p/boBae29AB9

OK, a couple of things.

First, you can't sensibly use a busy loop in a production code
(actually, in any code). It should basically lock an OS thread on
which the goroutine executing it happened to be scheduled.

What you should use instead is one of agreed upon methods of
synchronization between goroutines: channels or wait groups.
Your main() most probably should just wait on some synchronization
resource while all the other work is to be done on other goroutines.

As to how exactly approach the problem depends on how gracefully you
want to tear down the services provided by your process (that HTTP
server and TCP server etc). The simplest way is to just set up a
handler for TERM (and INT) signals which would simply call os.Exit(0).
That would bring the whole thing down brutally killing all the
goroutines without any chance for then for cleaning up. A more
involved approach would be to broadcast a "shutdown signal" to all the
"servicing" goroutines expecting them to tear their own subordinate
goroutines down than send an acknowledgement signal back; after
collecting all the ACK signals, the controlling goroutine might safely
exit. You might hand-roll this mechanism or use something already made
like [1]. In either case, you won't probably be able to use
http.ListenAndServe() directly in such "graceful" way because there's
no way for it to be controlled from the outside. Instead, you'd need
to first create a listener and then write a custom code that would
`select` both on a listener and a synchronization channel delivering
those "shutdown signals" discussed, and after accepting another
connection spawn a goroutine serving the request (i.e. do what
http.ListenAndServe() does, and more).

1. http://blog.labix.org/2011/10/09/death-of-goroutines-under-control

Mikhail Vitsen

unread,
Nov 10, 2014, 5:06:02 PM11/10/14
to golan...@googlegroups.com, mikhai...@gmail.com
Thanks a lot - your recomenrations are very useful! I've remade some thing in my server to make it work as you said.
Also - i fixed the problem! http://play.golang.org/p/rlR1gmQbIk

I replaced the strings

server := &http.Server{Addr: ":8080"}
go server
.ListenAndServe()

with 

server := &http.Server{}
listener
, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 8080})


if err != nil {
 log
.Fatal("error creating listener")
}
 
go server
.Serve(listener)


and it works fine! But i can't understand what was wrong in the first variant! I used code from sources of http (func (srv *Server) ListenAndServe(), but without tcpKeepAliveListener structure) - it is nearly the same!

dja...@gmail.com

unread,
Nov 10, 2014, 7:28:53 PM11/10/14
to golan...@googlegroups.com, mikhai...@gmail.com

Konstantin Khomoutov

unread,
Nov 13, 2014, 8:39:54 AM11/13/14
to Mikhail Vitsen, golan...@googlegroups.com
On Mon, 10 Nov 2014 14:06:02 -0800 (PST)
Mikhail Vitsen <mikhai...@gmail.com> wrote:

> Thanks a lot - your recomenrations are very useful! I've remade some
> thing in my server to make it work as you said.
> Also - i fixed the problem! http://play.golang.org/p/rlR1gmQbIk
>
> I replaced the strings
>
> server := &http.Server{Addr: ":8080"}
> go server.ListenAndServe()
>
> with
>
> server := &http.Server{}
> listener, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.IPv4(127,
> 0, 0, 1), Port: 8080})
>
>
> if err != nil {
> log.Fatal("error creating listener")
> }
>
> go server.Serve(listener)
>
>
> and it works fine! But i can't understand what was wrong in the first
> variant! I used code from sources of http (func (srv *Server)
> ListenAndServe(), but without tcpKeepAliveListener structure) - it is
> nearly the same!

I'm iclined to think that merely replacing for {} with select {} -- as
djadala showed you -- would fix your case even with the original

go server.ListenAndServe()

Please forget about bloody busy loops--the only (somewhat) valid case
for them is code handling the hardware (i.e. in-kernel drivers) because
sometimes there's just no way to wait on some signal from that hardware
for the completion of the command issued to it, so you just have to
wait (poll) until the state changes. Go is for higher-level concurrent
programming so all the code which has to wait on some other code to
complete has to use one or another synchronization mechanism provided
by Go. Busy loops is not one of them.
Reply all
Reply to author
Forward
0 new messages