Why does go log.Fatal(someserver.ListenAndServe()) not work as expected?

I had to change go log.Fatal(someserver.ListenAndServe()) to go func() { log.Fatal(someserver.ListenAndServe()) }() and I don’t understand why there’s a difference.

What did you expect to happen?

What happened instead?

Hey @versine,

The question I’d be asking here is what was the reason you had to change it for?

It sounds to me like you had other code written underneath your call to ListenAndServe which wasn’t being executed, so if this is the problem, this is because ListenAndServe is a blocking call and by running it in a separate goroutine, it is no longer blocking and code following it can execute regularly.

Sorry, I should have been more clear. If I run go log.Fatal(svr.ListenAndServe()), the server is inaccessible and doesn’t accept connections, whereas if I run go func() { log.Fatal(svr.ListenAndServe()) }(), then the server works as expected. It appears the first version of the code also blocks. My intuition is that both versions of the code should launch log.Fatal(svr.ListenAndServe()) in a separate gouroutine.

More concretely,

This works:

package main

import (
“fmt”
“log”
“net/http”
)

func main() {
http.HandleFunc(“/”, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, “hello”)
})
go func() { log.Fatal(http.ListenAndServe(“:8080”, nil)) }()

select {}

}

while this doesn’t:

package main

import (
“fmt”
“log”
“net/http”
)

func main() {
http.HandleFunc(“/”, func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, “hello”)
})
go log.Fatal(http.ListenAndServe(“:8080”, nil))

select {}

}

Oh ok I understand what you are asking, however, both of these versions I just wrote are working fine for me:

1:

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, World!")
	})
	go log.Fatal(http.ListenAndServe(":9000", nil))
	select {}
}
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, World!")
	})
	go func() {
		log.Fatal(http.ListenAndServe(":9000", nil))
	}()
	select {}
}

So this leads me to ask the following

  1. How are you exiting your program?
  2. If you run the second example on a different port, what happens if nothing else has previously run on that port, does it now work?
  3. Are you receiving any errors from ListenAndServe in the second example?

Hmm, it appears I accidentally typed the wrong url when testing the first one. However, you can see that the version without the extra func blocks if, for example, you insert fmt.Println("hi") after the go and before the select.

This program gets killed by the engine: https://play.golang.org/p/J6b5xORFIL

And this one just terminates: https://play.golang.org/p/MdUPuv64d2

Yes, this is expected. I’ll try to explain it in easy terms.

When calling this:

go func() {
	log.Fatal(http.ListenAndServe(":9000", nil))
}()

You are creating a new goroutine and then inside of the goroutine calling a function that would normally be blocking, therefore it is no longer blocking.

However, when calling it like this:

go log.Fatal(http.ListenAndServe(":9000", nil))

It is essentially the same as this:

go func(err error) {
	log.Fatal(err)
}(http.ListenAndServe(":9000", nil))

Where a new goroutine that accepts an error as a parameter is being called, however to pass it the error value, you are trying to evaluate a blocking call, ListenAndServe, to retrieve the error value that it returns to pass to the goroutine as a parameter, so it will continue to block unless ListenAndServe fails and returns an error.

6 Likes

Oh, that makes sense. So because it needs the return value of a blocking call to pass to the goroutine, it blocks forever. Thank you, it seems obvious now that I see it.

3 Likes

That’s ok, I hope what I wrote was easy enough to understand, because after re-reading it, I realized that I probably could have explained it a little better :stuck_out_tongue:

It’s one of those things that’s not too obvious thing until you understand what is happening anyway.

I like the way you explained it above since that alone is probably easier to understand than what I wrote, but glad it made sense anyway :stuck_out_tongue:

It was good. The example helped a lot. It’s very intuitive now that I understand what’s going on. The magic of go just threw me off a bit.

Ok, good to hear :slight_smile: Although I wouldn’t really call this a magic issue, but more rather something that requires a better understanding of the syntax to understand, which now you have :stuck_out_tongue:

Just one thing though, in my example I used the error in the goroutine to then call log.Fatal with the error, however the goroutine function itself would actually be the call to log.Fatal so technically I should have written something like this since it is closer to what is actually happening, but I’m sure you get the point anyway :smiley:

go func(err error) {
	log.Output(2, err.Error())
	os.Exit(1)
}(http.ListenAndServe(":9000", nil))

Yeah, I know it wasn’t supposed to be perfectly identical to the other code. And I didn’t mean they’re magical in a bad way :stuck_out_tongue: Thanks again.

Haha all good :stuck_out_tongue: Also, no problem and anytime!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.