-module(gen_call).
|
|
|
|
-export([gcall/3, gcall/4, greply/2]).
|
|
|
|
-define(default_timeout, 5000).
|
|
|
|
%% We trust the arguments to be correct, i.e
|
|
%% Process is either a local or remote pid,
|
|
%% or a {Name, Node} tuple (of atoms) and in this
|
|
%% case this node (node()) _is_ distributed and Node =/= node().
|
|
-define(get_node(Process), case Process of {_S, N} -> N; _ -> node(Process) end).
|
|
|
|
gcall(Process, Label, Request) ->
|
|
gcall(Process, Label, Request, ?default_timeout).
|
|
|
|
gcall(Process, Label, Request, Timeout) ->
|
|
%%-----------------------------------------------------------------
|
|
%% Map different specifications of a process to either Pid or
|
|
%% {Name,Node}. Execute the given Fun with the process as only
|
|
%% argument.
|
|
%% -----------------------------------------------------------------
|
|
case where(Process) of
|
|
undefined ->
|
|
exit(noproc);
|
|
PidOrNameNode ->
|
|
%Node = ?get_node(PidOrNameNode),
|
|
try do_call(PidOrNameNode, Label, Request, Timeout)
|
|
catch
|
|
exit:{nodedown, _Node} ->
|
|
%% A nodedown not yet detected by global, pretend that it was.
|
|
exit(noproc)
|
|
end
|
|
end.
|
|
|
|
-dialyzer({no_improper_lists, do_call/4}).
|
|
do_call(Process, Label, Request, Timeout) ->
|
|
CurNode = node(),
|
|
case ?get_node(Process) of
|
|
CurNode ->
|
|
Mref = erlang:monitor(process, Process),
|
|
%% Local without timeout; no need to use alias since we unconditionally
|
|
%% will wait for either a reply or a down message which corresponds to
|
|
%% the process being terminated (as opposed to 'noconnection')...
|
|
case self() of
|
|
Process ->
|
|
exit(calling_self);
|
|
_ ->
|
|
Process ! {Label, {self(), Mref}, Request},
|
|
end,
|
|
receive
|
|
{Mref, Reply} ->
|
|
erlang:demonitor(Mref, [flush]),
|
|
{ok, Reply};
|
|
{'DOWN', Mref, _, _, Reason} ->
|
|
exit(Reason)
|
|
after Timeout ->
|
|
erlang:demonitor(Mref, [flush]),
|
|
receive
|
|
{[alias | Mref], Reply} ->
|
|
{ok, Reply}
|
|
after 0 ->
|
|
exit(timeout)
|
|
end
|
|
end;
|
|
_PNode ->
|
|
Mref = erlang:monitor(process, Process, [{alias, demonitor}]),
|
|
Tag = [alias | Mref],
|
|
|
|
%% OTP-24:
|
|
%% Using alias to prevent responses after 'noconnection' and timeouts.
|
|
%% We however still may call nodes responding via process identifier, so
|
|
%% we still use 'noconnect' on send in order to try to send on the
|
|
%% monitored connection, and not trigger a new auto-connect.
|
|
%%
|
|
erlang:send(Process, {Label, {self(), Tag}, Request}, [noconnect]),
|
|
|
|
receive
|
|
{[alias | Mref], Reply} ->
|
|
erlang:demonitor(Mref, [flush]),
|
|
{ok, Reply};
|
|
{'DOWN', Mref, _, _, noconnection} ->
|
|
exit({nodedown, _PNode});
|
|
{'DOWN', Mref, _, _, Reason} ->
|
|
exit(Reason)
|
|
after Timeout ->
|
|
erlang:demonitor(Mref, [flush]),
|
|
receive
|
|
{[alias | Mref], Reply} ->
|
|
{ok, Reply}
|
|
after 0 ->
|
|
exit(timeout)
|
|
end
|
|
end
|
|
end.
|
|
|
|
where({global, Name}) -> global:whereis_name(Name);
|
|
where({local, Name}) -> whereis(Name);
|
|
where({via, Module, Name}) -> Module:whereis_name(Name);
|
|
where({Name, Node} = Process) ->
|
|
CurNode = node(),
|
|
case CurNode of
|
|
Node ->
|
|
whereis(Name);
|
|
nonode@nohost ->
|
|
exit({nodedown, Node});
|
|
_ when is_atom(Name), is_atom(Node) ->
|
|
Process;
|
|
_ ->
|
|
undefined
|
|
end;
|
|
where(Name) ->
|
|
if
|
|
is_pid(Name) ->
|
|
Name;
|
|
is_atom(Name) ->
|
|
whereis(Name);
|
|
true ->
|
|
undefined
|
|
end.
|
|
|
|
%%
|
|
%% Send a reply to the client.
|
|
%%
|
|
greply({_To, [alias | Alias] = Tag}, Reply) when is_reference(Alias) ->
|
|
Alias ! {Tag, Reply}, ok;
|
|
greply({_To, [[alias | Alias] | _] = Tag}, Reply) when is_reference(Alias) ->
|
|
Alias ! {Tag, Reply}, ok;
|
|
greply({To, Tag}, Reply) ->
|
|
try To ! {Tag, Reply}, ok catch _:_ -> ok end.
|