pkaramol
pkaramol

Reputation: 19402

Using buffered channel for errors in go when listening and serving

I am going through a tutorial on building web servers using go.

The author, instead of directly using the http.ListenAndServe() method, he creates the http.Server struct.

He then proceeds by:

The reason behind using a buffered channel is according to the instructor

so that the goroutine can exit if we do not collect this error

There is indeed below in a program a select block where the errors coming from this channel are being collected.

Can anyone pls help me understand how the goroutine gets to exit if we don't collect the error?

What would be the practical difference had we used an unbuffered channel?

Upvotes: 4

Views: 1343

Answers (2)

colm.anseo
colm.anseo

Reputation: 22117

Short answer:

For any channel (buffered or not), channel reads block if nothing is written to the channel.

For non-buffered channels, channel writes will block if no one is listening.

It is a common technique with error-channels (since only one item will ever be written to the channel), to make it a buffered channel of size 1. It ensures the write will happen without blocking - and the writer goroutine can continue on its way and return.

Therefore the service does not relying on the client caller reading from the error channel to perform its cleanup.

Note: to reclaim a channel re GC, it only has to go out of scope - it does not need to be fully drained. Nor does it need to be closed. Once it goes out of scope from the both ends, it will be GC'ed.

Upvotes: 3

shmsr
shmsr

Reputation: 4204

If you refer the code for ListenAndServe(), you'll notice the following comments on how it works. Quoting from there itself:

// ListenAndServe always returns a non-nil error. After Shutdown or Close,
// the returned error is ErrServerClosed.

Also,

// When Shutdown is called, Serve, ListenAndServe, and
// ListenAndServeTLS immediately return ErrServerClosed. Make sure the
// program doesn't exit and waits instead for Shutdown to return.


Your select block is waiting for Shutdown (error) considering that you're gracefully handling the server's shutdown and doesn't let the goroutine exit before it gracefully closes.

In the case of func (srv *Server) Close() (eg. Most use defer srv.Close(), right?):

// Close immediately closes all active net.Listeners and any
// connections in state StateNew, StateActive, or StateIdle. For a

// Close returns any error returned from closing the Server's
// underlying Listener(s).
// graceful shutdown, use Shutdown.

So, the same explanation as above carries of using the select block.


Now, let's categorize channels as buffered and unbuffered, and if we do care about the guarantee of delivery of the signal (communication with the channel), then unbuffered one ensures it. Whereas, if the buffered channel (size = 1) which is in your case, then it ensures delivery but might be delayed.

Let's elaborate unbuffered channels:

A send operation on an unbuffered channel blocks the sending goroutine until another 
goroutine executes a corresponding receive on that same channel, at which point the value 
is transmitted and both goroutines may continue

Conversely, if received on the channel earlier (<-chan) than send operation, then the 
receiving goroutine is blocked until the corresponding send operation occurs on the 
same channel on another goroutine.

Aforementioned points for unbuffered channels indicate synchronous nature.

Remember, func main() is also a goroutine.

Let's elaborate buffered channels:

A send operation on a buffered channel pushes an element at the back of the queue, 
and a receive operation pops an element from the front of the queue. 
 1. If the channel is full, the send operation blocks its goroutine until space is made available by another goroutine's receive. 
 2. If the channel is empty, a receive operation blocks until a value is sent by another goroutine.

So in your case size of the channel is 1. The other sender goroutine can send in a non-blocking manner as the receiver channel of the other goroutine dequeues it as soon as it receives. But, if you remember, I mentioned delayed delivery for the channel with size 1 as we don't how much time it'll take for the receiver channel goroutine to return.

Hence, to block the sender goroutine, select block is used. And from the referenced code's documentation, you can see

// Make sure the program doesn't exit and waits instead for Shutdown to return.

Also, for more clarity, you can refer: Behaviour of channels The author explains it with pure clarity.

Upvotes: 1

Related Questions