R13B04 inet_res:resolve/4 inet_udp Port leak

Bob Ippolito bob@REDACTED
Wed May 26 02:00:39 CEST 2010


It appears that there may be an inet_udp Port leak in
inet_res:resolve/4, our current workaround is to spawn a new process
to call this function. We've noticed this primarily for a service that
regularly does a UDP DNS query that fails (because the response is too
big) and then we retry over TCP.

This is what the state of the process looked like when it was leaking ports:

(node@REDACTED)1> length(lists:filter(fun erlang:is_port/1, element(2,
erlang:process_info(whereis(dns_gen_server), links)))).
577
(node@REDACTED)2> lists:usort([erlang:port_info(P, name) || P <-
lists:filter(fun erlang:is_port/1, element(2,
erlang:process_info(whereis(dns_gen_server), links)))]).
[{name,"udp_inet"}]

The code looked like this, before the workaround was implemented:

%% @spec dns(string()) -> [string()]
%% @doc Return the A records (IPv4 IPs) as strings for the given Host name.
%%     This may return an empty list if there no A records for this Host name.
dns(Host) when is_list(Host) ->
    dns(Host, fun inet_res:resolve/4).

dns(Host, ResolveFun) ->
    case ResolveFun(Host, in, a, []) of
        {ok, Msg} ->
            ips_for_answers(Msg);
        {error, {nxdomain, _}} ->
            [];
        {error, timeout} ->
            %% retry with TCP
            case ResolveFun(Host, in, a, [{usevc, true}]) of
                {ok, Msg} ->
                    ips_for_answers(Msg);
                {error, {nxdomain, _}} ->
                    [];
                Error = {error, _} ->
                    Error
            end;
        Error = {error, _} ->
            Error
    end.

ips_for_answers(Msg) ->
    [inet_parse:ntoa(inet_dns:rr(Answer, data))
     || Answer <- inet_dns:msg(Msg, anlist)].

The workaround we used was to call it indirectly with this function, I
couldn't find anything in OTP that did the same thing that didn't have
local call optimizations.

%% @spec process_apply(atom(), atom(), [term()]) -> term()
%% @doc erlang:apply(M, F, A) in a temporary process and return the results.
process_apply(M,F,A) ->
    %% We can't just use rpc here because there's a local call optimization.
    Parent = self(),
    Fun = fun () ->
                  try
                      Parent ! {self(), erlang:apply(M, F, A)}
                  catch
                      Class:Reason ->
                          Stacktrace = erlang:get_stacktrace(),
                          Parent ! {self(), Class, Reason, Stacktrace}
                  end
          end,
    {Pid, Ref} = erlang:spawn_monitor(Fun),
    receive
        {Pid, Res} ->
            receive {'DOWN', Ref, process, Pid, _} -> ok end,
            Res;
        {Pid, Class, Reason, Stacktrace} ->
            receive {'DOWN', Ref, process, Pid, _} -> ok end,
            erlang:error(erlang:raise(Class, Reason, Stacktrace));
        {'DOWN', Ref, process, Pid, Reason} ->
            erlang:exit(Reason)
    end.


More information about the erlang-bugs mailing list