My thought about DNS resolving inside Mojolicious

INTRODUCTION

As you all know Mojolicious uses non-blocking IO for network operations. This allows Mojo::Server::* to process many requests simultaneously, make many http requests with Mojo::UserAgent in parallel and other cool things.

But there is one hickey which may block whole non-blocking app with Mojolicious. It is known as host name resolution. For now Mojolicious uses blocking host name resolution provided by IO::Socket::* backend. This is gethostbyname(3) for IO::Socket::INET and getaddrinfo(3) for IO::Socket::IP. This is not the problem if you write web application which will just handle requests and will not produce any requests to internet itself. But if your application actively uses Mojo::UserAgent or Mojo::IOLoop::Client, for example, and your DNS server is not so fast, whole application may suffer and even misbehave. You may think that this is not the problem because your DNS server is fast enough: it resolves google.com to ip in 0.001 sec. But in fact your DNS server may return result for one domain in 0.001 sec and in 30 sec for other. No any warranty here. And all 30 sec your application will be blocked.

But why till now no one has written non-blocking DNS resolver for Mojolicious? In fact Mojolicious had non-blocking resolver since v0.999931 till v2.27. But as sri said this resolver sucked. This is why it was removed. And the only available alternative, as sri said, is to use getaddrinfo(3) from separate thread. This is how i started Net::DNS::Native few weeks ago.

POSSIBLE SOLUTION

I think it will be cool to make pluggable API for DNS resolver, something similar to Mojo::Reactor. The idea is to make Mojo::Resolver base class with only one usefull method detect() which will detect and load proper subclass. And two virtual methods resolve() and cancel() which should be overloaded in subclass:

$id = resolve($host, $record_types, $port, $cb($result, $error))

resolve() should translate $host to ip address(es) in convenient form. For IPv6 (IO::Socket::IP) this should be result in form of Socket::getaddrinfo(), so we can use PeerAddrInfo option of IO::Socket::IP. For IPv4 (IO::Socket::INET) this should be one ip in xxx.xxx.xxx.xxx form, so we can use PeerAddr option of IO::Socket::INET.

$record_types is array ref with possible values 'A' or 'AAAA' or both. $port is port number to pass to getaddrinfo(3) (optional, if only 'A' record type specified). $cb is callback which will be called on finish.

Two parameters will be passed to callback. $result is undef on error, 4 dotted ip as string if only 'A' record type specified, array ref with same values as Socket::getaddrinfo() would return if $record_types contains 'AAAA' record type. If $result is undefined $error should contain error message. resolve() should return unique id associated with this operation.

cancel($id)

cancel() should stop resolving operation associated with $id. This is usefull for timeouts.

Now we have base class and should implement some subclasses. Like Mojo::Reactor has Mojo::Reactor::Poll and Mojo::Reactor::EV we need to implement one subclass which will be default and one more powerfull which may require additional module.

Not sure about names, but let's call default resolver as Mojo::Resolver::Default. And this will be just simple class which will call blocking Socket::inet_aton() or Socket::getaddrinfo() depending on record type. So in fact this will be same behavior as now.

And other subclass should be really non-blocking. Let's call it Mojo::Resolver::Native with dependency from Net::DNS::Native.

Then we'll add resolver attribute to Mojo::IOLoop and correct a little Mojo::IOLoop::Client::connect() to use this resolver.

So, now we have optional non-blocking resolver in the core and pluggable API, so anyone can write cpan module like Mojo::Resolver::AnyEvent or Mojo::Resolver::NetDNS or other.

Comments (1)

Oleg says:
Sun, 09 Nov 2014
And finally it is there: https://github.com/kraih/mojo/commit/5180ae376f75091db92b566fefc26899e55b8681 ;)

Add comment

Fields marked * are required.
This form has a bot protection mechanism, that requires Cookies.
Please, don't disable them.



Gravatar-friendly




Paragraphs are created automatically. Available tags: [quote], [code].