%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2006-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %%%------------------------------------------------------------------- %%% File : signal_SUITE.erl %%% Author : Rickard Green %%% Description : Test signals %%% %%% Created : 10 Jul 2006 by Rickard Green %%%------------------------------------------------------------------- -module(signal_SUITE). -author('rickard.s.green@ericsson.com'). %-define(line_trace, 1). -include_lib("common_test/include/ct.hrl"). -export([all/0, suite/0,init_per_suite/1, end_per_suite/1]). -export([init_per_testcase/2, end_per_testcase/2]). % Test cases -export([xm_sig_order/1, pending_exit_unlink_process/1, pending_exit_unlink_dist_process/1, pending_exit_unlink_port/1, pending_exit_trap_exit/1, pending_exit_receive/1, pending_exit_exit/1, pending_exit_gc/1, pending_exit_is_process_alive/1, pending_exit_process_display/1, pending_exit_process_info_1/1, pending_exit_process_info_2/1, pending_exit_group_leader/1, exit_before_pending_exit/1]). init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) -> available_internal_state(true), [{testcase, Func}|Config]. end_per_testcase(_Func, _Config) -> ok. init_per_suite(Config) -> Config. end_per_suite(_Config) -> available_internal_state(true), catch erts_debug:set_internal_state(not_running_optimization, true), available_internal_state(false). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap, {minutes, 2}}]. all() -> [xm_sig_order, pending_exit_unlink_process, pending_exit_unlink_dist_process, pending_exit_unlink_port, pending_exit_trap_exit, pending_exit_receive, pending_exit_trap_exit, pending_exit_gc, pending_exit_is_process_alive, pending_exit_process_display, pending_exit_process_info_1, pending_exit_process_info_2, pending_exit_group_leader, exit_before_pending_exit]. %% Test that exit signals and messages are received in correct order xm_sig_order(Config) when is_list(Config) -> LNode = node(), repeat(fun () -> xm_sig_order_test(LNode) end, 1000), {ok, RNode} = start_node(Config), repeat(fun () -> xm_sig_order_test(RNode) end, 1000), stop_node(RNode), ok. xm_sig_order_test(Node) -> P = spawn(Node, fun () -> xm_sig_order_proc() end), M = erlang:monitor(process, P), P ! may_reach, P ! may_reach, P ! may_reach, exit(P, good_signal_order), P ! may_not_reach, P ! may_not_reach, P ! may_not_reach, receive {'DOWN', M, process, P, R} -> good_signal_order = R end. xm_sig_order_proc() -> receive may_not_reach -> exit(bad_signal_order); may_reach -> ok after 0 -> ok end, xm_sig_order_proc(). pending_exit_unlink_process(Config) when is_list(Config) -> pending_exit_test(self(), unlink). pending_exit_unlink_dist_process(Config) when is_list(Config) -> {ok, Node} = start_node(Config), From = spawn(Node, fun () -> receive after infinity -> ok end end), Res = pending_exit_test(From, unlink), stop_node(Node), Res. pending_exit_unlink_port(Config) when is_list(Config) -> pending_exit_test(hd(erlang:ports()), unlink). pending_exit_trap_exit(Config) when is_list(Config) -> pending_exit_test(self(), trap_exit). pending_exit_receive(Config) when is_list(Config) -> pending_exit_test(self(), 'receive'). pending_exit_exit(Config) when is_list(Config) -> pending_exit_test(self(), exit). pending_exit_gc(Config) when is_list(Config) -> pending_exit_test(self(), gc). pending_exit_test(From, Type) -> case catch erlang:system_info(smp_support) of true -> OTE = process_flag(trap_exit, true), Ref = make_ref(), Master = self(), ExitBySignal = case Type of gc -> lists:duplicate(10000, exit_by_signal); _ -> exit_by_signal end, Pid = spawn_link( fun () -> receive go -> ok end, false = have_pending_exit(), exit = fake_exit(From, self(), ExitBySignal), true = have_pending_exit(), Master ! {self(), Ref, Type}, case Type of gc -> force_gc(), erlang:yield(); unlink -> unlink(From); trap_exit -> process_flag(trap_exit, true); 'receive' -> receive _ -> ok after 0 -> ok end; exit -> ok end, exit(exit_by_myself) end), Mon = erlang:monitor(process, Pid), Pid ! go, Reason = receive {'DOWN', Mon, process, Pid, R} -> receive {Pid, Ref, Type} -> ok after 0 -> ct:fail(premature_exit) end, case Type of exit -> exit_by_myself = R; _ -> ExitBySignal = R end end, receive {'EXIT', Pid, R2} -> Reason = R2 end, process_flag(trap_exit, OTE), ok, {comment, "Test only valid with current SMP emulator."}; _ -> {skipped, "SMP support not enabled. Test only valid with current SMP emulator."} end. exit_before_pending_exit(Config) when is_list(Config) -> %% This is a testcase testcase very specific to the smp %% implementation as it is of the time of writing. %% %% The testcase tries to check that a process can %% exit by itself even though it has a pending exit. OTE = process_flag(trap_exit, true), Master = self(), Tester = spawn_link( fun () -> Opts = case {erlang:system_info(run_queues), erlang:system_info(schedulers_online)} of {RQ, SO} when RQ =:= 1; SO =:= 1 -> []; _ -> process_flag(scheduler, 1), [{scheduler, 2}] end, P = self(), Exiter = spawn_opt(fun () -> receive {exit_me, P, R} -> exit(P, R) end end, Opts), erlang:yield(), Exiter ! {exit_me, self(), exited_by_exiter}, %% We want to get a pending exit %% before we exit ourselves. We %% don't want to be scheduled out %% since we will then see the %% pending exit. %% %% Do something that takes %% relatively long time but %% consumes few reductions... repeat(fun() -> erlang:system_info(procs) end,10), %% ... then exit. Master ! {self(), pending_exit, have_pending_exit()}, exit(exited_by_myself) end), PendingExit = receive {Tester, pending_exit, PE} -> PE end, receive {'EXIT', Tester, exited_by_myself} -> process_flag(trap_exit, OTE), ok; Msg -> ct:fail({unexpected_message, Msg}) end, NoScheds = integer_to_list(erlang:system_info(schedulers_online)), {comment, "Was " ++ case PendingExit of true -> ""; false ->"*not*" end ++ " able to trigger a pending exit. " ++ "Running on " ++ NoScheds ++ " scheduler(s). " ++ "This test is only interesting with at least two schedulers."}. -define(PE_INFO_REPEAT, 100). pending_exit_is_process_alive(Config) when is_list(Config) -> S = exit_op_test_init(), TestFun = fun (P) -> false = is_process_alive(P) end, repeated_exit_op_test(TestFun, ?PE_INFO_REPEAT), verify_pending_exit_success(S), comment(). pending_exit_process_info_1(Config) when is_list(Config) -> S = exit_op_test_init(), TestFun = fun (P) -> undefined = process_info(P) end, repeated_exit_op_test(TestFun, ?PE_INFO_REPEAT), verify_pending_exit_success(S), comment(). pending_exit_process_info_2(Config) when is_list(Config) -> S0 = exit_op_test_init(), repeated_exit_op_test(fun (P) -> undefined = process_info(P, messages) end, ?PE_INFO_REPEAT), S1 = verify_pending_exit_success(S0), repeated_exit_op_test(fun (P) -> undefined = process_info(P, status) end, ?PE_INFO_REPEAT), S2 = verify_pending_exit_success(S1), repeated_exit_op_test(fun (P) -> undefined = process_info(P, links) end, ?PE_INFO_REPEAT), S3 = verify_pending_exit_success(S2), repeated_exit_op_test(fun (P) -> undefined = process_info(P, [messages]) end, ?PE_INFO_REPEAT), S4 = verify_pending_exit_success(S3), repeated_exit_op_test(fun (P) -> undefined = process_info(P, [status]) end, ?PE_INFO_REPEAT), S5 = verify_pending_exit_success(S4), repeated_exit_op_test(fun (P) -> undefined = process_info(P, [links]) end, ?PE_INFO_REPEAT), S6 = verify_pending_exit_success(S5), repeated_exit_op_test(fun (P) -> undefined = process_info(P, [status, links]) end, ?PE_INFO_REPEAT), S7 = verify_pending_exit_success(S6), repeated_exit_op_test(fun (P) -> undefined = process_info(P, [messages, status]) end, ?PE_INFO_REPEAT), S8 = verify_pending_exit_success(S7), repeated_exit_op_test(fun (P) -> undefined = process_info(P, [messages, links]) end, ?PE_INFO_REPEAT), S9 = verify_pending_exit_success(S8), repeated_exit_op_test( fun (P) -> undefined = process_info(P, [message_queue_len, status]) end, ?PE_INFO_REPEAT), S10 = verify_pending_exit_success(S9), repeated_exit_op_test(fun (P) -> undefined = process_info(P, [messages, links, status]) end, ?PE_INFO_REPEAT), verify_pending_exit_success(S10), comment(). pending_exit_process_display(Config) when is_list(Config) -> S = exit_op_test_init(), TestFun = fun (P) -> badarg = try erlang:process_display(P, backtrace) catch error:badarg -> badarg end end, repeated_exit_op_test(TestFun, ?PE_INFO_REPEAT), verify_pending_exit_success(S), comment(). pending_exit_group_leader(Config) when is_list(Config) -> S = exit_op_test_init(), TestFun = fun (P) -> badarg = try group_leader(self(), P) catch error:badarg -> badarg end end, repeated_exit_op_test(TestFun, ?PE_INFO_REPEAT), verify_pending_exit_success(S), comment(). %% %% -- Internal utils -------------------------------------------------------- %% exit_op_test_init() -> put(no_pending_exit_success, 0), put(no_pending_exit_tries, 0), {case {erlang:system_info(run_queues), erlang:system_info(schedulers_online)} of {RQ, SO} when RQ =:= 1; SO =:= 1 -> false; _ -> true end, 0, 0}. verify_pending_exit_success({false, _, _} = S) -> S; verify_pending_exit_success({true, S, T}) -> NewS = get(no_pending_exit_success), NewT = get(no_pending_exit_tries), case NewT =:= T of true -> ok; _ -> case NewS > S of true -> ok; _ -> exit(no_pending_exits) end end, {true, NewS, NewT}. comment() -> {comment, "Pending exit trigger ratio " ++ integer_to_list(get(no_pending_exit_success)) ++ "/" ++ integer_to_list(get(no_pending_exit_tries)) ++ "." ++ case get(not_running_opt_test) of true -> " No 'not running optimization' to disable."; _ -> "" end}. repeated_exit_op_test(TestFun, N) -> WorkFun0 = fun () -> lists:sort(lists:reverse(lists:seq(1, 1000))) end, repeat(fun () -> exit_op_test(TestFun, WorkFun0) end, N), try erts_debug:set_internal_state(not_running_optimization, false) of Bool when Bool == true; Bool == false -> WorkFun1 = fun () -> erts_debug:set_internal_state(sleep, 0), lists:sort(lists:reverse(lists:seq(1, 1000))) end, repeat(fun () -> exit_op_test(TestFun, WorkFun1) end, N) catch error:notsup -> put(not_running_opt_test, true) after catch erts_debug:set_internal_state(not_running_optimization, true) end. exit_op_test(TestFun, WorkFun) -> Opts = case {erlang:system_info(run_queues), erlang:system_info(schedulers_online)} of {RQ, SO} when RQ =:= 1; SO =:= 1 -> []; _ -> process_flag(scheduler, 1), [{scheduler, 2}] end, Master = self(), Going = make_ref(), P = spawn_opt(fun () -> loop(10, WorkFun), Master ! Going, loop(infinity, WorkFun) end, Opts), receive Going -> ok end, loop(10, WorkFun), erlang:yield(), exit(P, bang), PE0 = have_pending_exit(P), TestFun(P), PE = case PE0 of true -> true; _ -> false end, case {PE, get(no_pending_exit_success), get(no_pending_exit_tries)} of {true, undefined, undefined} -> put(no_pending_exit_success, 1), put(no_pending_exit_tries, 1); {false, undefined, undefined} -> put(no_pending_exit_success, 0), put(no_pending_exit_tries, 1); {true, S, T} -> put(no_pending_exit_success, S+1), put(no_pending_exit_tries, T+1); {false, _S, T} -> put(no_pending_exit_tries, T+1) end, ok. loop(infinity, WorkFun) -> do_loop(infinity, WorkFun); loop(0, _WorkFun) -> ok; loop(N, WorkFun) when is_integer(N) -> do_loop(N-1, WorkFun). do_loop(N, WorkFun) -> WorkFun(), loop(N, WorkFun). repeat(_Fun, N) when is_integer(N), N =< 0 -> ok; repeat(Fun, N) when is_integer(N) -> Fun(), repeat(Fun, N-1). start_node(Config) -> Name = list_to_atom(atom_to_list(?MODULE) ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config)) ++ "-" ++ integer_to_list(erlang:system_time(second)) ++ "-" ++ integer_to_list(erlang:unique_integer([positive]))), Pa = filename:dirname(code:which(?MODULE)), test_server:start_node(Name, slave, [{args, "-pa " ++ Pa}]). stop_node(Node) -> test_server:stop_node(Node). have_pending_exit() -> have_pending_exit(self()). have_pending_exit(Pid) -> erts_debug:get_internal_state({have_pending_exit, Pid}). force_gc() -> erts_debug:set_internal_state(force_gc, self()). fake_exit(From, To, Reason) -> erts_debug:set_internal_state(send_fake_exit_signal, {From, To, Reason}). available_internal_state(Bool) when Bool == true; Bool == false -> case {Bool, (catch erts_debug:get_internal_state(available_internal_state))} of {true, true} -> true; {false, true} -> erts_debug:set_internal_state(available_internal_state, false), true; {true, _} -> erts_debug:set_internal_state(available_internal_state, true), false; {false, _} -> false end.