Skip to content

Instantly share code, notes, and snippets.

@spacejam
Last active June 19, 2018 12:59
Show Gist options
  • Save spacejam/dd5901c8b2920bb0f1ec to your computer and use it in GitHub Desktop.
Save spacejam/dd5901c8b2920bb0f1ec to your computer and use it in GitHub Desktop.
erlang net_kernel:connect_node/1 code dive

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.

otp/lib/kernel/src/erl_ddll.erl

-spec monitor(Tag, Item) -> MonitorRef when
      Tag :: driver,
      Item :: {Name, When},
      Name :: driver(),
      When :: loaded | unloaded | unloaded_only,
      MonitorRef :: reference().

monitor(_, _) ->
    erlang:nif_error(undef).

c in otp/erts/emulator... TBD

@toraritte
Copy link

Thanks a lot for this research! It's just what I needed to find out the difference between net_kernel:connect/1 and net_kernel:connect_node/1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment