src/swirl_code_server.erl

-module(swirl_code_server).
-include("swirl.hrl").

%% public
-export([
    get_module/3,
    start_link/0,
    version/1
]).

-behaviour(gen_server).
-export([
    init/1,
    handle_call/3,
    handle_cast/2,
    handle_info/2,
    terminate/2,
    code_change/3
]).

-define(TABLE_OPTS, [public, named_table, {write_concurrency, true}]).
-define(SERVER, ?MODULE).

-record(state, {}).

%% public
-spec get_module(node(), module(), module_vsn()) -> ok.
get_module(Node, Module, ModuleVsn) ->
    KeyVal = {{Module, ModuleVsn}, true},
    case ets:insert_new(?TABLE_NAME_CODE_SERVER, KeyVal) of
        true ->
            message(Node, {get_module, Module, ModuleVsn, node()});
        false -> ok
    end.

-spec start_link() -> {ok, pid()}.
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

-spec version(module()) -> {ok, module_vsn()} | {error, term()}.
version(Module) ->
    try Module:module_info(attributes) of
        Attributes ->
            [ModuleVsn] = ?L(vsn, Attributes),
            {ok, ModuleVsn}
    catch
        error:undef ->
            {error, undef}
    end.

%% gen_server callbacks
init([]) ->
    process_flag(trap_exit, true),
    swirl_ets_manager:table(?TABLE_NAME_CODE_SERVER, ?TABLE_OPTS, ?SERVER),
    {ok, #state {}}.

handle_call(Request, _From, State) ->
    io:format("unexpected message: ~p~n", [Request]),
    {reply, ok, State}.

handle_cast(Msg, State) ->
    io:format("unexpected message: ~p~n", [Msg]),
    {noreply, State}.

handle_info({'ETS-TRANSFER', _TableId, _Pid, _Data}, State) ->
    {noreply, State};
handle_info({get_module, Module, ModuleVsn, Node}, State) ->
    {ok, ModuleVsn} = version(Module),
    {Module, Binary, Filename} = code:get_object_code(Module),
    message(Node, {load_module, Module, Binary, Filename}),
    {noreply, State};
handle_info({load_module, Module, Binary, Filename}, State) ->
    {module, Module} = code:load_binary(Module, Filename, Binary),
    {noreply, State};
handle_info(Info, State) ->
    io:format("unexpected message: ~p~n", [Info]),
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% private
message(Node, Msg) ->
    {?SERVER, Node} ! Msg,
    ok.