Skip to content

Instantly share code, notes, and snippets.

@toraritte
Forked from spacejam/gist:dd5901c8b2920bb0f1ec
Last active November 20, 2018 13:08
Show Gist options
  • Save toraritte/2eddbbddddc4841d403c to your computer and use it in GitHub Desktop.
Save toraritte/2eddbbddddc4841d403c to your computer and use it in GitHub Desktop.
net_kernel:connect/1 VS net_kernel:connect_node/1

=== CALL TRACE OF net_kernel:connect\1

% otp/lib/kernel/src/net_kernel.erl
connect(node_name@machine) -> do_connect(node_name@machine, normal, false). ->
  %          Node,              Type,   WaitForBarred
  do_connect(node_name@machine, normal, false           ) -> %% Type = normal | hidden
    case catch ets:lookup(sys_dist, Node) of
        % [{connection, ...}] if node is in sys_dist
        % []                  if not
	{'EXIT', _} ->
	[#barred_connection{}] ->
	Else ->
	    case application:get_env(kernel, dist_auto_connect) of
                % so far I always got undefined
		{ok, never} ->
		{ok, once} when Else =/= [],
				(hd(Else))#connection.state =:= up ->
                % from this point on it is the same as connect_node/1
		_ ->
		    request({connect, Type=normal, Node})

=== CALL TRACE OF net_kernel:connect_node\1

% otp/lib/kernel/src/net_kernel.erl
net_kernel:connect_node(node_name@machine) ->
  net_kernel:request({connect, normal, node_name@machine}) ->
    gen_server:call(net_kernel,Req = {connect, normal, node_name@machine},infinity) ->
      % otp/lib/stdlib/src/gen_server.erl
      %                Name       Request                               Timeout
      gen_server:call(net_kernel, {connect, normal, node_name@machine}, infinity) ->
        case catch gen:call(Name, '$gen_call', Request, Timeout) of
          {ok, Res} -> Res;
        % otp/lib/stdlib/src/gen.erl
        %        Name,       Label,       Request,                              Timeout
        gen:call(net_kernel, '$gen_call', {connect, normal, node_name@machine}, infinity)
          %           Pid,                 Label,       Request,                              Timeout
          gen:do_call(whereis(net_kernel), '$gen_call', {connect, normal, node_name@machine}, infinity);
            try erlang:monitor(process, Process) of
              Mref ->
                catch erlang:send(Process, {Label, {self(), Mref}, Request},[noconnect]),
                receive
                  {Mref, Reply} ->
                     erlang:demonitor(Mref, [flush]),
                     {ok, Reply};
           % otp/lib/stdlib/src/gen_server.erl
           % {ok,Reply} -> Reply is false if node doesn't exist
           %                        true if it does and is up, atom spelled correctly etc.
           case catch gen:call(Name, '$gen_call', Request, Timeout) of
             {ok, Res} -> Res;

otp/lib/kernel/src/net_kernel.erl

-spec connect_node(Node) -> boolean() | ignored when
  Node :: node().
%% explicit connects
connect_node(Node) when is_atom(Node) ->
  request({connect, normal, Node}).

otp/lib/kernel/src/net_kernel.erl

%% If the net_kernel isn't running we ignore all requests to the
%% kernel, thus basically accepting them :-)
request(Req) ->
  case whereis(net_kernel) of
    P when is_pid(P) ->
      gen_server:call(net_kernel,Req,infinity);
    _ -> ignored
  end.

otp/lib/stdlib/src/gen_server.erl

%% -----------------------------------------------------------------
%% Make a call to a generic server.
%% If the server is located at another node, that node will
%% be monitored.
%% If the client is trapping exits and is linked server termination
%% is handled here (? Shall we do that here (or rely on timeouts) ?).
%% -----------------------------------------------------------------
call(Name, Request) ->
    case catch gen:call(Name, '$gen_call', Request) of
  {ok,Res} ->
      Res;
  {'EXIT',Reason} ->
      exit({Reason, {?MODULE, call, [Name, Request]}})
    end.

call(Name, Request, Timeout) ->
    case catch gen:call(Name, '$gen_call', Request, Timeout) of
  {ok,Res} ->
      Res;
  {'EXIT',Reason} ->
      exit({Reason, {?MODULE, call, [Name, Request, Timeout]}})
    end.

otp/lib/stdlib/src/gen.erl

%%-----------------------------------------------------------------
%% Makes a synchronous call to a generic process.
%% Request is sent to the Pid, and the response must be
%% {Tag, _, Reply}.
%%-----------------------------------------------------------------

%%% New call function which uses the new monitor BIF
%%% call(ServerId, Label, Request)

call(Process, Label, Request) ->
    call(Process, Label, Request, ?default_timeout).

%% Local or remote by pid
call(Pid, Label, Request, Timeout)
  when is_pid(Pid), Timeout =:= infinity;
       is_pid(Pid), is_integer(Timeout), Timeout >= 0 ->
    do_call(Pid, Label, Request, Timeout);
%% Local by name
call(Name, Label, Request, Timeout)
  when is_atom(Name), Timeout =:= infinity;
       is_atom(Name), is_integer(Timeout), Timeout >= 0 ->
    case whereis(Name) of
  Pid when is_pid(Pid) ->
      do_call(Pid, Label, Request, Timeout);
  undefined ->
      exit(noproc)
    end;
%% Global by name
call(Process, Label, Request, Timeout)
  when ((tuple_size(Process) == 2 andalso element(1, Process) == global)
  orelse
    (tuple_size(Process) == 3 andalso element(1, Process) == via))
       andalso
       (Timeout =:= infinity orelse (is_integer(Timeout) andalso Timeout >= 0)) ->
    case where(Process) of
  Pid when is_pid(Pid) ->
      Node = node(Pid),
      try do_call(Pid, Label, Request, Timeout)
      catch
    exit:{nodedown, Node} ->
        %% A nodedown not yet detected by global,
        %% pretend that it was.
        exit(noproc)
      end;
  undefined ->
      exit(noproc)
    end;
%% Local by name in disguise
call({Name, Node}, Label, Request, Timeout)
  when Node =:= node(), Timeout =:= infinity;
       Node =:= node(), is_integer(Timeout), Timeout >= 0 ->
    call(Name, Label, Request, Timeout);
%% Remote by name
call({_Name, Node}=Process, Label, Request, Timeout)
  when is_atom(Node), Timeout =:= infinity;
       is_atom(Node), is_integer(Timeout), Timeout >= 0 ->
    if
  node() =:= nonode@nohost ->
      exit({nodedown, Node});
  true ->
      do_call(Process, Label, Request, Timeout)
    end.

do_call(Process, Label, Request, Timeout) ->
    try erlang:monitor(process, Process) of
  Mref ->
      %% If the monitor/2 call failed to set up a connection to a
      %% remote node, we don't want the '!' operator to attempt
      %% to set up the connection again. (If the monitor/2 call
      %% failed due to an expired timeout, '!' too would probably
      %% have to wait for the timeout to expire.) Therefore,
      %% use erlang:send/3 with the 'noconnect' option so that it
      %% will fail immediately if there is no connection to the
      %% remote node.

      catch erlang:send(Process, {Label, {self(), Mref}, Request},
      [noconnect]),
      receive
    {Mref, Reply} ->
        erlang:demonitor(Mref, [flush]),
        {ok, Reply};
    {'DOWN', Mref, _, _, noconnection} ->
        Node = get_node(Process),
        exit({nodedown, Node});
    {'DOWN', Mref, _, _, Reason} ->
        exit(Reason)
      after Timeout ->
        erlang:demonitor(Mref, [flush]),
        exit(timeout)
      end
    catch
  error:_ ->
      %% Node (C/Java?) is not supporting the monitor.
      %% The other possible case -- this node is not distributed
      %% -- should have been handled earlier.
      %% Do the best possible with monitor_node/2.
      %% This code may hang indefinitely if the Process
      %% does not exist. It is only used for featureweak remote nodes.
      Node = get_node(Process),
      monitor_node(Node, true),
      receive
    {nodedown, Node} ->
        monitor_node(Node, false),
        exit({nodedown, Node})
      after 0 ->
        Tag = make_ref(),
        Process ! {Label, {self(), Tag}, Request},
        wait_resp(Node, Tag, Timeout)
      end
    end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment