DNS resolution in a typical application is a blocking operation. This is especially problematic if your program is event driven as you can cause the run loop to stall while waiting for a response. RubyDNS provides a fully featured asynchronous DNS resolver built on top of EventMachine, and can be used to minimise the latency of name resolution in your program.
In your own code you'd probably want to use the new
resolver.addresses_for method which helpfully returns a list of addresses.
Event driving programming is pretty straight forward. You essentially have a loop that reads events and responds to them. Processing is kept to a minimum per cycle, otherwise the loop stalls and becomes unresponsive.
Software that uses a network for communication typically relies on name resolution. In particular,
RubyDNS::Server is primarily interested in two things - receiving incoming requests and sending out a response - and - sending out requests and waiting for a response.
DNS resolution is one task that is typically done using operating system functions such as
getaddrinfo. The main problem is that these functions cause your process to sleep until a result is available.
(Have you ever noticed how sometimes in games, the entire game stalls when logging in or listing servers? This is very often due to name resolution latency affecting the main event loop)
If RubyDNS uses these functions, no other events can be processed while we are waiting for the operating system to respond. In practice, this means that
RubyDNS::Server may perform poorly if many people are using it simultaneously.
To avoid these problems, RubyDNS recently introduced its own
RubyDNS::Resolver which provides robust asynchronous DNS resolution built on top of EventMachine. This resolver isn't just for
RubyDNS::Server though, it can be used in any EventMachine event driven code that wants high performance name resolution.
Given a request, which consists of one or more DNS questions, our resolver firstly checks whether UDP is a suitable transport. DNS packets are typically routed over UDP but if the packet is too big it should use TCP:
With this list of candidates, we connect to each one and send the request. In all failure cases, we try the next server if one is available, otherwise if no servers have been successful we signal a resolution failure:
We then wait until EventMachine tells us one of two things: some response was received or there was a timeout. If we receive a response, as long as it wasn't truncated, we are successful:
In practice, we use
EventMachine::Deferrable to handle this signalling. Using deferrables ultiamtely led to concise and reliable code and I was very happy with the results. I'd recommend taking a look at the full source code.