使用erlang开发服务端の代码热升级

来源:互联网 发布:linux find 文件 编辑:程序博客网 时间:2024/06/03 19:29

之前写过一个简单的使用ranch tcp开发服务端,http://blog.csdn.net/huang1196/article/details/38401197

运行代码后发现如果对源文件修改并make之后,代码不会自动热升级。实际上当然不会这样,一直这么认为的原因在于用mochiweb开发时每次在eclipse下保存,console都会马上提示reloading XXX,对代码进行热升级;或者直接make,也能进行代码热升级。实际上mochiweb用了个简单的reloader实现了此功能,我们只需要把其源码copy到自己的项目中并在启动服务端时将reloader一同启动即可。


start.sh

erl -pa ebin deps/*/ebin +K true +P 199999 \      -sname game_server \      -s game -s reloader


其实现很好理解,通过code:all_loaded()得到所有Module对应的文件名,通过其修改时间判断该文件是否被更改,若更改则通过code:purge/1移除旧code,通过code:load_file/1加载新code。


reloader.erl

%% @copyright 2007 Mochi Media, Inc.%% @author Matthew Dempsky <matthew@mochimedia.com>%%%% @doc Erlang module for automatically reloading modified modules%% during development.-module(reloader).-author("Matthew Dempsky <matthew@mochimedia.com>").-include_lib("kernel/include/file.hrl").-behaviour(gen_server).-export([start/0, start_link/0]).-export([stop/0]).-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).-export([all_changed/0]).-export([is_changed/1]).-export([reload_modules/1]).-record(state, {last, tref}).%% External API%% @spec start() -> ServerRet%% @doc Start the reloader.start() ->    gen_server:start({local, ?MODULE}, ?MODULE, [], []).%% @spec start_link() -> ServerRet%% @doc Start the reloader.start_link() ->    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).%% @spec stop() -> ok%% @doc Stop the reloader.stop() ->    gen_server:call(?MODULE, stop).%% gen_server callbacks%% @spec init([]) -> {ok, State}%% @doc gen_server init, opens the server in an initial state.init([]) ->    {ok, TRef} = timer:send_interval(timer:seconds(1), doit),    {ok, #state{last = stamp(), tref = TRef}}.%% @spec handle_call(Args, From, State) -> tuple()%% @doc gen_server callback.handle_call(stop, _From, State) ->    {stop, shutdown, stopped, State};handle_call(_Req, _From, State) ->    {reply, {error, badrequest}, State}.%% @spec handle_cast(Cast, State) -> tuple()%% @doc gen_server callback.handle_cast(_Req, State) ->    {noreply, State}.%% @spec handle_info(Info, State) -> tuple()%% @doc gen_server callback.handle_info(doit, State) ->    Now = stamp(),    _ = doit(State#state.last, Now),    {noreply, State#state{last = Now}};handle_info(_Info, State) ->    {noreply, State}.%% @spec terminate(Reason, State) -> ok%% @doc gen_server termination callback.terminate(_Reason, State) ->    {ok, cancel} = timer:cancel(State#state.tref),    ok.%% @spec code_change(_OldVsn, State, _Extra) -> State%% @doc gen_server code_change callback (trivial).code_change(_Vsn, State, _Extra) ->    {ok, State}.%% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}]%% @doc code:purge/1 and code:load_file/1 the given list of modules in order,%%      return the results of code:load_file/1.reload_modules(Modules) ->    [begin code:purge(M), code:load_file(M) end || M <- Modules].%% @spec all_changed() -> [atom()]%% @doc Return a list of beam modules that have changed.all_changed() ->    [M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)].%% @spec is_changed(atom()) -> boolean()%% @doc true if the loaded module is a beam with a vsn attribute%%      and does not match the on-disk beam file, returns false otherwise.is_changed(M) ->    try        module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M))    catch _:_ ->            false    end.%% Internal APImodule_vsn({M, Beam, _Fn}) ->    {ok, {M, Vsn}} = beam_lib:version(Beam),    Vsn;module_vsn(L) when is_list(L) ->    {_, Attrs} = lists:keyfind(attributes, 1, L),    {_, Vsn} = lists:keyfind(vsn, 1, Attrs),    Vsn.doit(From, To) ->    [case file:read_file_info(Filename) of         {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To ->             reload(Module);         {ok, _} ->             unmodified;         {error, enoent} ->             %% The Erlang compiler deletes existing .beam files if             %% recompiling fails.  Maybe it's worth spitting out a             %% warning here, but I'd want to limit it to just once.             gone;         {error, Reason} ->             io:format("Error reading ~s's file info: ~p~n",                       [Filename, Reason]),             error     end || {Module, Filename} <- code:all_loaded(), is_list(Filename)].reload(Module) ->    io:format("Reloading ~p ...", [Module]),    code:purge(Module),    case code:load_file(Module) of        {module, Module} ->            io:format(" ok.~n"),            case erlang:function_exported(Module, test, 0) of                true ->                    io:format(" - Calling ~p:test() ...", [Module]),                    case catch Module:test() of                        ok ->                            io:format(" ok.~n"),                            reload;                        Reason ->                            io:format(" fail: ~p.~n", [Reason]),                            reload_but_test_failed                    end;                false ->                    reload            end;        {error, Reason} ->            io:format(" fail: ~p.~n", [Reason]),            error    end.stamp() ->    erlang:localtime().%%%% Tests%%-ifdef(TEST).-include_lib("eunit/include/eunit.hrl").-endif.





0 0