Hacker News new | ask | show | jobs
by tptacek 4650 days ago
I know it's irrational, but it drives me a little nuts how the proxy idiom in go is "two goroutines implementing socket-to-socket copy". The inner handler loop of a proxy is a place where, to me, select/poll might actually make the code easier to follow; also, the idiom doubles the number of goroutines required to handle a given connection load, and while goroutines are cheap, they aren't free.

I know it's possible to pull select() into Golang programs (I ended up having to, to write a fast port scanner), but Golang people look at you weirdly when you tell them you did that.

3 comments

Go is strongly opinionated and very normative (which is a good thing for its main target audience). It's not surprising it attracts people who would look down to you for departing from the norm, even if your choices are technically justified.
Do you have any examples or data to support that there is any benefit by employing a explicit asynchronous IO pattern rather than relying on the Go IO libraries perform the select() (epoll or whatever...) syscall and then rely on the goroutine scheduler to switch control to who is registered to do something when the data is ready?

Is the default minimal stack segment size too big? Or the scheduler too heavyweight?

In the case of a proxy, the select(2) call implements the simple predicate "should I read from the client or the server". It turns a pair of coroutines into a single normal routine. The single routine is clearer, perhaps marginally less performant for a single connection, but probably marginally more performant across a large number of connections.

The "asynchronous pattern" thing is a religious canard. Golang devotees are (rightfully) happy to abandon event-structured programs in favor of programs that look like socket tutorial code but perform like event servers. But we're not talking about asynchronous control; we're talking about a synchronous loop. I've noticed this when talking to Golang people: they hear "select" or "poll" and automatically a switch goes off in their head that lights the "BAD!" lamp. I'm not sure that's valid.

Another example, which I gave upthread, was a high-speed port scanner; when I left it to the Golang scheduler to handle the sockets without Golang's timeout idiom, I quickly starved the program of sockets, because of the interaction between timeouts and the scheduler. I pulled select(2) into the program (for that one use only! Just to avoid using Golang timeouts for a simple connect(2) timeout) and the program not only ran quickly but properly handled the socket descriptors.

This isn't a critique of Golang, which I like working with. Rather, I'm criticizing a specific Golang idiom.

I'm sorry, I'm not sure I follow you. Is the problem with your high speed port scanner lie in the implementation of DialTimeout or you for some reason you had to use syscall.Connect in a new goroutine and implemented the timeouts with select{} ?
errrm, why not just `func Copy(dst Writer, src Reader) (written int64, err error)` ? avoid having to select/poll and just let the io.Reader and io.Writer interfaces do the work for you... and if they implemenet WriteTo, all the better.
I've written a lot of code using select and poll, and I don't think it's ever come out "simpler and easier to follow" than code which just used blocking I/O.
We're not talking about the same thing. I'm not suggesting that Golang would be better off with fully evented I/O. In fact, the comment I wrote that you replied to agrees with what you just said.