1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2012-2013. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: 
   20: -module(code_parallel_load_SUITE).
   21: -export([
   22: 	all/0,
   23: 	suite/0,
   24: 	init_per_suite/1,
   25: 	end_per_suite/1,
   26: 	init_per_testcase/2,
   27: 	end_per_testcase/2
   28:     ]).
   29: 
   30: -export([
   31: 	multiple_load_check_purge_repeat/1,
   32: 	many_load_distributed_only_once/1
   33:     ]).
   34: 
   35: -define(model,       code_parallel_load_SUITE_model).
   36: -define(interval,    50).
   37: -define(number_of_processes, 160).
   38: -define(passes, 4).
   39: 
   40: 
   41: -include_lib("test_server/include/test_server.hrl").
   42: 
   43: suite() -> [{ct_hooks,[ts_install_cth]}].
   44: 
   45: all() ->
   46:     [
   47: 	multiple_load_check_purge_repeat,
   48: 	many_load_distributed_only_once
   49:     ].
   50: 
   51: 
   52: init_per_suite(Config) ->
   53:     Config.
   54: 
   55: end_per_suite(_Config) ->
   56:     ok.
   57: 
   58: init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
   59:     Dog=?t:timetrap(?t:minutes(3)),
   60:     [{watchdog, Dog}|Config].
   61: 
   62: end_per_testcase(_Func, Config) ->
   63:     SConf = ?config(save_config, Config),
   64:     Pids  = proplists:get_value(purge_pids, SConf),
   65: 
   66:     case check_old_code(?model) of
   67: 	true -> check_and_purge_processes_code(Pids, ?model);
   68: 	_ ->    ok
   69:     end,
   70:     case erlang:delete_module(?model) of
   71: 	true -> check_and_purge_processes_code(Pids, ?model);
   72: 	_ ->    ok
   73:     end,
   74:     Dog=?config(watchdog, Config),
   75:     ?t:timetrap_cancel(Dog).
   76: 
   77: 
   78: multiple_load_check_purge_repeat(_Conf) ->
   79:     Ts    = [v1,v2,v3,v4,v5,v6],
   80: 
   81:     %% generate code that receives a token, code switches to new code
   82:     %% then matches this token against a literal code token
   83:     %% should be identical
   84:     %% (smoke test for parallel code loading
   85:     Codes = [{T, generate(?model, [], [
   86: 	    format("check(T) -> receive {_Pid, change, T1} -> "
   87: 		" ~w:check(T1)\n"
   88: 		" after 0 -> T = f(), check(T) end.\n", [?model]),
   89: 	    format("f() -> ~w.~n", [T])
   90: 	])} || T <- Ts],
   91: 
   92:     Pids = setup_code_changer(Codes),
   93:     {save_config, [{purge_pids,Pids}]}.
   94: 
   95: setup_code_changer([{Token,Code}|Cs] = Codes) ->
   96:     {module, ?model} = erlang:load_module(?model,Code),
   97:     Pids = setup_checkers(Token,?number_of_processes),
   98:     code_changer(Cs, Codes, ?interval,Pids,?passes),
   99:     Pids.
  100: 
  101: code_changer(_, _, _, Pids, 0) ->
  102:     [unlink(Pid) || Pid <- Pids],
  103:     [exit(Pid, die) || Pid <- Pids],
  104:     io:format("done~n"),
  105:     ok;
  106: code_changer([], Codes, T, Pids, Ps) ->
  107:     code_changer(Codes, Codes, T, Pids, Ps - 1);
  108: code_changer([{Token,Code}|Cs], Codes, T, Pids, Ps) ->
  109:     receive after T ->
  110: 	    io:format("load code with token ~4w : pass ~4w~n", [Token, Ps]),
  111: 	    {module, ?model} = erlang:load_module(?model, Code),
  112: 	    % this is second time we call load_module for this module
  113: 	    % so it should have old code
  114: 	    [Pid ! {self(), change, Token} || Pid <- Pids],
  115: 	    % should we wait a moment or just blantantly try to check and purge repeatadly?
  116: 	    receive after 1 -> ok end,
  117: 	    ok = check_and_purge_processes_code(Pids, ?model),
  118: 	    code_changer(Cs, Codes, T, Pids, Ps)
  119:     end.
  120: 
  121: 
  122: 
  123: many_load_distributed_only_once(_Conf) ->
  124:     Ts = [<<"first version">>, <<"second version">>],
  125: 
  126:     [{Token1,Code1},{Token2, Code2}] = [{T, generate(?model, [], [
  127: 	    "check({<<\"second version\">> = V, Pid}) -> V = f(), Pid ! {self(), completed, V}, ok;\n" ++
  128: 	    format("check(T) -> receive {Pid, change, T1, B} -> "
  129: 		" Res = erlang:load_module(~w, B), Pid ! {self(), change, Res},\n"
  130: 		" ~w:check({T1, Pid})\n"
  131: 		" after 0 -> T = f(), check(T) end.\n", [?model, ?model]),
  132: 	    format("f() -> ~w.~n", [T])
  133: 	])} || T <- Ts],
  134: 
  135: 
  136:     {module, ?model} = erlang:load_module(?model, Code1),
  137:     Pids = setup_checkers(Token1,?number_of_processes),
  138: 
  139:     receive after 1000 -> ok end, % give 'em some time to spin up
  140:     [Pid ! {self(), change, Token2, Code2} || Pid <- Pids],
  141:     Loads = [receive {Pid, change, Res} -> Res end || Pid <- Pids],
  142:     [receive {Pid, completed, Token2} -> ok end || Pid <- Pids],
  143: 
  144:     ok = ensure_only_one_load(Loads, 0),
  145:     {save_config, [{purge_pids,Pids}]}.
  146: 
  147: ensure_only_one_load([], 1) -> ok;
  148: ensure_only_one_load([], _) -> too_many_loads;
  149: ensure_only_one_load([{module, ?model}|Loads], N) ->
  150:     ensure_only_one_load(Loads, N + 1);
  151: ensure_only_one_load([{error, not_purged}|Loads], N) ->
  152:     ensure_only_one_load(Loads, N).
  153: % no other return values are allowed from load_module
  154: 
  155: 
  156: %% aux
  157: 
  158: setup_checkers(_,0) -> [];
  159: setup_checkers(T,N) -> [spawn_link(fun() -> ?model:check(T) end) | setup_checkers(T, N-1)].
  160: 
  161: check_and_purge_processes_code(Pids, M) ->
  162:     check_and_purge_processes_code(Pids, M, []).
  163: check_and_purge_processes_code([], M, []) ->
  164:     erlang:purge_module(M),
  165:     ok;
  166: check_and_purge_processes_code([], M, Pending) ->
  167:     io:format("Processes ~w are still executing old code - retrying.~n", [Pending]),
  168:     check_and_purge_processes_code(Pending, M, []);
  169: check_and_purge_processes_code([Pid|Pids], M, Pending) ->
  170:     case erlang:check_process_code(Pid, M) of
  171: 	false ->
  172: 	    check_and_purge_processes_code(Pids, M, Pending);
  173: 	true ->
  174: 	    check_and_purge_processes_code(Pids, M, [Pid|Pending])
  175:     end.
  176: 
  177: 
  178: generate(Module, Attributes, FunStrings) ->
  179:     FunForms = function_forms(FunStrings),
  180:     Forms    = [
  181: 	{attribute,1,module,Module},
  182: 	{attribute,2,export,[FA || {FA,_} <- FunForms]}
  183:     ] ++ [{attribute, 3, A, V}|| {A, V} <- Attributes] ++
  184:     [ Function || {_, Function} <- FunForms],
  185:     {ok, Module, Bin} = compile:forms(Forms),
  186:     Bin.
  187: 
  188: 
  189: function_forms([]) -> [];
  190: function_forms([S|Ss]) ->
  191:     {ok, Ts,_} = erl_scan:string(S),
  192:     {ok, Form} = erl_parse:parse_form(Ts),
  193:     Fun   = element(3, Form),
  194:     Arity = element(4, Form),
  195:     [{{Fun,Arity}, Form}|function_forms(Ss)].
  196: 
  197: format(F,Ts) -> lists:flatten(io_lib:format(F, Ts)).