Accepting Sockets Asynchronously with prim_inet
September 6, 2015
As I was going over my HTTP/2 in Erlang talk for StrangeLoop, Sean Cribbs showed me a way to listen asynchronously for a socket. It's a pretty clean method, but I had to do some digging in order to get it working, because it uses the prim_inet module, which isn't very well documented. I think that might be because we're not supposed to use it. Oops.
So, suppose you've already opened a socket with gen_tcp:listen. It'a actually very important that you use gen_tcp:listen and not ssl:listen. We'll upgrade to SSL later if that's important to you, and it should be.
{ok, Ref} = prim_inet:async_accept(ListenSocket, -1),
First of all, Ref isn't a reference(). Sorry about that. I actually have no idea what it is, but it'll be the same value that we bind to Ref again later. When's later? When a client tries to connect. When that happens you'll get a process message that looks like this:
{inet_async, ListenSocket, Ref, {ok, CliSocket}}
ListenSocket is the same one you passed in. Ref, like I mentioned, is the same value that was returned from prim_inet:async_accept/2. It's {ok, CliSocket} that we want to work with.
We're certainly not done. When you try to perform any gen_tcp functions on CliSocket, it all explodes. gen_tcp:accept and ssl:accept regester the port as a socket with the inet_db for you, but since we went around those, we're going to have to register it manually:
inet_db:register_socket(CliSocket, inet_tcp),
Great. Now all these gen_tcp options work! But, you'll need to do a little more if you want this to be a SSL connection.
{ok, AcceptSocket} = ssl:ssl_accept(CliSocket, SSLOptions),
Once this is done, you can perform ssl functions on the socket.
How I Used It
In chatterbox, each http2_connection FSM started like this:
- Supervisor passes an open socket (started with either
ssl:listenorgen_tcp:listen) toinit/1 - In order to not hang in the
init/1callback, add the Listener to the FSM state and transition to anacceptstate with a0timeout - use a case statement to call either
gen_tcp:accept/1orssl:transport_accept/1, and hang waiting on a connection.
That's how it was before this commit when I moved over to the prim_inet solution. Now it works more like:
- Supervisor still passes an open socket, but now it's always opened with
gen_tcp:listen init/1callsprim_inet:async_accept/2- The half of my
accept/2callback was refactored intohandle_info/2, because now it's reacting to a message fromprim_inet, and not a0timeout. - The remaining half of my
accpet/2callback was renamed tohandshake/2to more accurately reflect it's role in the HTTP/2 spec.
This way, my FSM is no longer blocking on gen_tcp:accpet/1 or ssl:transport_accept/1, and can now receive messages. Now, I don't actually need to receive messages in this particular case, but it seems cleaner overall.