diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/rexi/ebin/rexi.app | 8 | ||||
-rw-r--r-- | apps/rexi/ebin/rexi.appup | 5 | ||||
-rw-r--r-- | apps/rexi/src/rexi.erl | 91 | ||||
-rw-r--r-- | apps/rexi/src/rexi_app.erl | 11 | ||||
-rw-r--r-- | apps/rexi/src/rexi_monitor.erl | 40 | ||||
-rw-r--r-- | apps/rexi/src/rexi_server.erl | 86 | ||||
-rw-r--r-- | apps/rexi/src/rexi_sup.erl | 15 |
7 files changed, 256 insertions, 0 deletions
diff --git a/apps/rexi/ebin/rexi.app b/apps/rexi/ebin/rexi.app new file mode 100644 index 00000000..620c8863 --- /dev/null +++ b/apps/rexi/ebin/rexi.app @@ -0,0 +1,8 @@ +{application, rexi, [ + {description, "Lightweight RPC server"}, + {vsn, "1.2"}, + {modules, [rexi, rexi_app, rexi_sup, rexi_monitor, rexi_server]}, + {registered, [rexi_sup, rexi_server]}, + {applications, [kernel, stdlib]}, + {mod, {rexi_app,[]}} +]}. diff --git a/apps/rexi/ebin/rexi.appup b/apps/rexi/ebin/rexi.appup new file mode 100644 index 00000000..7ed8ad73 --- /dev/null +++ b/apps/rexi/ebin/rexi.appup @@ -0,0 +1,5 @@ +{"1.1",[{"1.0",[ + {load_module, rexi}, + {add_module, rexi_monitor}, + {load_module, rexi_server} +]}],[{"1.0",[]}]}. diff --git a/apps/rexi/src/rexi.erl b/apps/rexi/src/rexi.erl new file mode 100644 index 00000000..8ab1f05e --- /dev/null +++ b/apps/rexi/src/rexi.erl @@ -0,0 +1,91 @@ +-module(rexi). +-export([start/0, stop/0, restart/0]). +-export([cast/2, cast/3, kill/2]). +-export([reply/1, sync_reply/1, sync_reply/2]). +-export([async_server_call/2, async_server_call/3]). + +-define(SERVER, rexi_server). + +start() -> + application:start(rexi). + +stop() -> + application:stop(rexi). + +restart() -> + stop(), start(). + +%% @equiv cast(Node, self(), MFA) +-spec cast(node(), {atom(), atom(), list()}) -> reference(). +cast(Node, MFA) -> + cast(Node, self(), MFA). + +%% @doc Executes apply(M, F, A) on Node. +%% You might want to use this instead of rpc:cast/4 for two reasons. First, +%% the Caller pid and the returned reference are inserted into the remote +%% process' dictionary as `rexi_from', so it has a way to communicate with you. +%% Second, the remote process is monitored. If it exits with a Reason other +%% than normal, Caller will receive a message of the form +%% `{Ref, {rexi_EXIT, Reason}}' where Ref is the returned reference. +-spec cast(node(), pid(), {atom(), atom(), list()}) -> reference(). +cast(Node, Caller, MFA) -> + Ref = make_ref(), + ok = gen_server:cast({?SERVER, Node}, {doit, {Caller,Ref}, MFA}), + Ref. + +%% @doc Sends an async kill signal to the remote process associated with Ref. +%% No rexi_EXIT message will be sent. +-spec kill(node(), reference()) -> ok. +kill(Node, Ref) -> + ok = gen_server:cast({?SERVER, Node}, {kill, Ref}). + +%% @equiv async_server_call(Server, self(), Request) +-spec async_server_call(pid() | {atom(),node()}, any()) -> reference(). +async_server_call(Server, Request) -> + async_server_call(Server, self(), Request). + +%% @doc Sends a properly formatted gen_server:call Request to the Server and +%% returns the reference which the Server will include in its reply. The +%% function acts more like cast() than call() in that the server process +%% is not monitored. Clients who want to know if the server is alive should +%% monitor it themselves before calling this function. +-spec async_server_call(pid() | {atom(),node()}, pid(), any()) -> reference(). +async_server_call(Server, Caller, Request) -> + Ref = make_ref(), + do_send(Server, {'$gen_call', {Caller,Ref}, Request}), + Ref. + +%% @doc convenience function to reply to the original rexi Caller. +-spec reply(any()) -> any(). +reply(Reply) -> + {Caller, Ref} = get(rexi_from), + erlang:send(Caller, {Ref,Reply}). + +%% @equiv sync_reply(Reply, 300000) +sync_reply(Reply) -> + sync_reply(Reply, 300000). + +%% @doc convenience function to reply to caller and wait for response. Message +%% is of the form {OriginalRef, {self(),reference()}, Reply}, which enables the +%% original caller to respond back. +-spec sync_reply(any(), pos_integer() | infinity) -> any(). +sync_reply(Reply, Timeout) -> + {Caller, Ref} = get(rexi_from), + Tag = make_ref(), + erlang:send(Caller, {Ref, {self(),Tag}, Reply}), + receive {Tag, Response} -> + Response + after Timeout -> + timeout + end. + +%% internal functions %% + +% send a message as quickly as possible +do_send(Dest, Msg) -> + case erlang:send(Dest, Msg, [noconnect]) of + noconnect -> + spawn(erlang, send, [Dest, Msg]); + ok -> + ok + end. diff --git a/apps/rexi/src/rexi_app.erl b/apps/rexi/src/rexi_app.erl new file mode 100644 index 00000000..dda57752 --- /dev/null +++ b/apps/rexi/src/rexi_app.erl @@ -0,0 +1,11 @@ +-module(rexi_app). +-behaviour(application). +-export([start/2, stop/1]). + +-include_lib("eunit/include/eunit.hrl"). + +start(_Type, StartArgs) -> + rexi_sup:start_link(StartArgs). + +stop(_State) -> + ok. diff --git a/apps/rexi/src/rexi_monitor.erl b/apps/rexi/src/rexi_monitor.erl new file mode 100644 index 00000000..bbff22b3 --- /dev/null +++ b/apps/rexi/src/rexi_monitor.erl @@ -0,0 +1,40 @@ +-module(rexi_monitor). +-export([start/1, stop/1]). + +-include_lib("eunit/include/eunit.hrl"). + +%% @doc spawn_links a process which monitors the supplied list of items and +%% returns the process ID. If a monitored process exits, the caller will +%% receive a {rexi_DOWN, MonitoringPid, DeadPid, Reason} message. +-spec start([pid() | atom() | {atom(),node()}]) -> pid(). +start(Procs) -> + Parent = self(), + spawn_link(fun() -> + [erlang:monitor(process, P) || P <- Procs], + wait_monitors(Parent) + end). + +%% @doc Cleanly shut down the monitoring process and flush all rexi_DOWN +%% messages from our mailbox. +-spec stop(pid()) -> ok. +stop(MonitoringPid) -> + MonitoringPid ! {self(), shutdown}, + flush_down_messages(). + +%% internal functions %% + +wait_monitors(Parent) -> + receive + {'DOWN', _, process, Pid, Reason} -> + Parent ! {rexi_DOWN, self(), Pid, Reason}, + wait_monitors(Parent); + {Parent, shutdown} -> + ok + end. + +flush_down_messages() -> + receive {rexi_DOWN, _, _, _} -> + flush_down_messages() + after 0 -> + ok + end. diff --git a/apps/rexi/src/rexi_server.erl b/apps/rexi/src/rexi_server.erl new file mode 100644 index 00000000..c4dc740d --- /dev/null +++ b/apps/rexi/src/rexi_server.erl @@ -0,0 +1,86 @@ +-module(rexi_server). +-behaviour(gen_server). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-export([start_link/0, init_p/2]). + +-include_lib("eunit/include/eunit.hrl"). + +-record(st, { + workers = ets:new(workers, [private, {keypos,2}]) +}). + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +init([]) -> + {ok, #st{}}. + +handle_call(_Request, _From, St) -> + {reply, ignored, St}. + +handle_cast({doit, From, MFA}, #st{workers=Workers} = St) -> + {LocalPid, Ref} = spawn_monitor(?MODULE, init_p, [From, MFA]), + {noreply, St#st{workers = add_worker({LocalPid, Ref, From}, Workers)}}; + +handle_cast({kill, FromRef}, #st{workers=Workers} = St) -> + case find_worker_from(FromRef, Workers) of + {Pid, KeyRef, {_, FromRef}} -> + erlang:demonitor(KeyRef), + exit(Pid, kill), + {noreply, St#st{workers = remove_worker(KeyRef, Workers)}}; + false -> + {noreply, St} + end. + +handle_info({'DOWN', Ref, process, _, normal}, #st{workers=Workers} = St) -> + {noreply, St#st{workers = remove_worker(Ref, Workers)}}; + +handle_info({'DOWN', Ref, process, Pid, Reason}, #st{workers=Workers} = St) -> + case find_worker(Ref, Workers) of + {Pid, Ref, From} -> + notify_caller(From, Reason), + {noreply, St#st{workers = remove_worker(Ref, Workers)}}; + false -> + {noreply, St} + end; + +handle_info(_Info, St) -> + {noreply, St}. + +terminate(_Reason, St) -> + ets:foldl(fun({Pid, _, _}, _) -> exit(Pid,kill) end, nil, St#st.workers), + ok. + +code_change(_OldVsn, St, _Extra) -> + {ok, St}. + +%% @doc initializes a process started by rexi_server. +-spec init_p({pid(), reference()}, {atom(), atom(), list()}) -> any(). +init_p(From, {M,F,A}) -> + put(rexi_from, From), + put(initial_call, {M,F,length(A)}), + try apply(M, F, A) catch _:Reason -> exit(Reason) end. + +%% internal + +add_worker(Worker, Tab) -> + ets:insert(Tab, Worker), Tab. + +remove_worker(Ref, Tab) -> + ets:delete(Tab, Ref), Tab. + +find_worker(Ref, Tab) -> + case ets:lookup(Tab, Ref) of [] -> false; [Worker] -> Worker end. + +find_worker_from(Ref, Tab) -> + case ets:match_object(Tab, {'_', '_', {'_', Ref}}) of + [] -> + false; + [Worker] -> + Worker + end. + +notify_caller({Caller, Ref}, Reason) -> + Caller ! {Ref, {rexi_EXIT, Reason}}. diff --git a/apps/rexi/src/rexi_sup.erl b/apps/rexi/src/rexi_sup.erl new file mode 100644 index 00000000..3a518e7b --- /dev/null +++ b/apps/rexi/src/rexi_sup.erl @@ -0,0 +1,15 @@ +-module(rexi_sup). +-behaviour(supervisor). +-export([init/1]). + +-export([start_link/1]). + +-include_lib("eunit/include/eunit.hrl"). + +start_link(Args) -> + supervisor:start_link({local,?MODULE}, ?MODULE, Args). + +init([]) -> + Mod = rexi_server, + Spec = {Mod, {Mod,start_link,[]}, permanent, 100, worker, [Mod]}, + {ok, {{one_for_one, 3, 10}, [Spec]}}. |