1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2006-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%%-------------------------------------------------------------------
22%%% File    : signal_SUITE.erl
23%%% Author  : Rickard Green <rickard.s.green@ericsson.com>
24%%% Description : Test signals
25%%%
26%%% Created : 10 Jul 2006 by Rickard Green <rickard.s.green@ericsson.com>
27%%%-------------------------------------------------------------------
28-module(signal_SUITE).
29-author('rickard.s.green@ericsson.com').
30
31%-define(line_trace, 1).
32-include_lib("common_test/include/ct.hrl").
33-export([all/0, suite/0,init_per_suite/1, end_per_suite/1]).
34-export([init_per_testcase/2, end_per_testcase/2]).
35
36% Test cases
37-export([xm_sig_order/1,
38         kill2killed/1,
39         contended_signal_handling/1,
40         busy_dist_exit_signal/1,
41         busy_dist_demonitor_signal/1,
42         busy_dist_down_signal/1,
43         busy_dist_spawn_reply_signal/1,
44         busy_dist_unlink_ack_signal/1]).
45
46init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
47    [{testcase, Func}|Config].
48
49end_per_testcase(_Func, _Config) ->
50    ok.
51
52init_per_suite(Config) ->
53    Config.
54
55end_per_suite(_Config) ->
56    ok.
57
58suite() ->
59    [{ct_hooks,[ts_install_cth]},
60     {timetrap, {minutes, 2}}].
61
62all() ->
63    [xm_sig_order,
64     kill2killed,
65     contended_signal_handling,
66     busy_dist_exit_signal,
67     busy_dist_demonitor_signal,
68     busy_dist_down_signal,
69     busy_dist_spawn_reply_signal,
70     busy_dist_unlink_ack_signal].
71
72%% Test that exit signals and messages are received in correct order
73xm_sig_order(Config) when is_list(Config) ->
74    LNode = node(),
75    repeat(fun () -> xm_sig_order_test(LNode) end, 1000),
76    {ok, RNode} = start_node(Config),
77    repeat(fun () -> xm_sig_order_test(RNode) end, 1000),
78    stop_node(RNode),
79    ok.
80
81
82xm_sig_order_test(Node) ->
83    P = spawn(Node, fun () -> xm_sig_order_proc() end),
84    M = erlang:monitor(process, P),
85    P ! may_reach,
86    P ! may_reach,
87    P ! may_reach,
88    exit(P, good_signal_order),
89    P ! may_not_reach,
90    P ! may_not_reach,
91    P ! may_not_reach,
92    receive
93	      {'DOWN', M, process, P, R} ->
94		  good_signal_order = R
95	  end.
96
97xm_sig_order_proc() ->
98    receive
99	may_not_reach -> exit(bad_signal_order);
100	may_reach -> ok
101    after 0 -> erlang:yield()
102    end,
103    xm_sig_order_proc().
104
105kill2killed(Config) when is_list(Config) ->
106    process_flag(trap_exit, true),
107    kill2killed_test(node()),
108    {ok, Node} = start_node(Config),
109    kill2killed_test(Node),
110    stop_node(Node),
111    ok.
112
113kill2killed_test(Node) ->
114    if Node == node() ->
115            io:format("Testing against local node", []);
116       true ->
117            io:format("Testing against remote node ~p", [Node])
118    end,
119    check_exit(Node, other_exit2, 1),
120    check_exit(Node, other_exit2, 2),
121    check_exit(Node, other_exit2, 9),
122    check_exit(Node, other_exit2, 10),
123    check_exit(Node, exit2, 1),
124    check_exit(Node, exit2, 2),
125    check_exit(Node, exit2, 9),
126    check_exit(Node, exit2, 10),
127    check_exit(Node, exit1, 1),
128    check_exit(Node, exit1, 2),
129    check_exit(Node, exit1, 9),
130    check_exit(Node, exit1, 10),
131    ok.
132
133check_exit(Node, Type, N) ->
134    io:format("Testing ~p length ~p~n", [Type, N]),
135    P = spawn_link_line(Node, node(), Type, N, self()),
136    if Type == other_exit2 ->
137            receive
138                {end_of_line, EOL} ->
139                    exit(EOL, kill)
140            end;
141       true -> ok
142    end,
143    receive
144        {'EXIT', P, Reason} ->
145            if Type == exit1 ->
146                    kill = Reason;
147               true ->
148                    killed = Reason
149            end
150    end.
151
152spawn_link_line(_NodeA, _NodeB, other_exit2, 0, Tester) ->
153    Tester ! {end_of_line, self()},
154    receive after infinity -> ok end;
155spawn_link_line(_NodeA, _NodeB, exit1, 0, _Tester) ->
156    exit(kill);
157spawn_link_line(_NodeA, _NodeB, exit2, 0, _Tester) ->
158    exit(self(), kill);
159spawn_link_line(NodeA, NodeB, Type, N, Tester) ->
160    spawn_link(NodeA,
161               fun () ->
162                       spawn_link_line(NodeB, NodeA, Type, N-1, Tester),
163                       receive after infinity -> ok end
164               end).
165
166contended_signal_handling(Config) when is_list(Config) ->
167    %%
168    %% Test for a race in signal handling of a process.
169    %%
170    %% When executing dirty, a "dirty signal handler"
171    %% process will handle signals for the process. If
172    %% the process stops executing dirty while the dirty
173    %% signal handler process is handling signals on
174    %% behalf of the process, both the dirty signal handler
175    %% process and the process itself might try to handle
176    %% signals for the process at the same time. There used
177    %% to be a bug that caused both processes to enter the
178    %% signal handling code simultaneously when the main
179    %% lock of the process was temporarily released during
180    %% signal handling (see GH-4885/OTP-17462/PR-4914).
181    %% Currently the main lock is only released when the
182    %% process receives an 'unlock' signal from a port,
183    %% and then responds by sending an 'unlock-ack' signal
184    %% to the port. This testcase tries to massage that
185    %% scenario. It is quite hard to cause a crash even
186    %% when the bug exists, but this testcase at least
187    %% sometimes causes a crash when the bug is present.
188    %%
189    process_flag(priority, high),
190    Drv = unlink_signal_drv,
191    ok = load_driver(Config, Drv),
192    try
193        contended_signal_handling_test(Drv, 250)
194    after
195        ok = erl_ddll:unload_driver(Drv)
196    end,
197    ok.
198
199contended_signal_handling_test(_Drv, 0) ->
200    ok;
201contended_signal_handling_test(Drv, N) ->
202    Ports = contended_signal_handling_make_ports(Drv, 100, []),
203    erlang:yield(),
204    contended_signal_handling_cmd_ports(Ports),
205    erts_debug:dirty_cpu(wait, rand:uniform(5)),
206    wait_until(fun () -> Ports == Ports -- erlang:ports() end),
207    contended_signal_handling_test(Drv, N-1).
208
209contended_signal_handling_cmd_ports([]) ->
210    ok;
211contended_signal_handling_cmd_ports([P|Ps]) ->
212    P ! {self(), {command, ""}},
213    contended_signal_handling_cmd_ports(Ps).
214
215contended_signal_handling_make_ports(_Drv, 0, Ports) ->
216    Ports;
217contended_signal_handling_make_ports(Drv, N, Ports) ->
218    Port = open_port({spawn, Drv}, []),
219    true = is_port(Port),
220    contended_signal_handling_make_ports(Drv, N-1, [Port|Ports]).
221
222busy_dist_exit_signal(Config) when is_list(Config) ->
223    BusyTime = 1000,
224    {ok, BusyChannelNode} = start_node(Config),
225    {ok, OtherNode} = start_node(Config, "-proto_dist gen_tcp"),
226    Tester = self(),
227    Exiter = spawn(BusyChannelNode,
228                   fun () ->
229                           pong = net_adm:ping(OtherNode),
230                           Tester ! {self(), alive},
231                           receive after infinity -> ok end
232                   end),
233    receive {Exiter, alive} -> ok end,
234    Linker = spawn_link(OtherNode,
235                        fun () ->
236                                process_flag(trap_exit, true),
237                                link(Exiter),
238                                receive
239                                    {'EXIT', Exiter, Reason} ->
240                                        tester_killed_me = Reason,
241                                        Tester ! {self(), got_exiter_exit_message};
242                                    Unexpected ->
243                                        exit({unexpected_message, Unexpected})
244                                end
245                         end),
246    make_busy(BusyChannelNode, OtherNode, 1000),
247    exit(Exiter, tester_killed_me),
248    receive
249        {Linker, got_exiter_exit_message} ->
250            unlink(Linker),
251            ok
252    after
253        BusyTime*2 ->
254            ct:fail(missing_exit_signal)
255    end,
256    stop_node(BusyChannelNode),
257    stop_node(OtherNode),
258    ok.
259
260busy_dist_demonitor_signal(Config) when is_list(Config) ->
261    BusyTime = 1000,
262    {ok, BusyChannelNode} = start_node(Config),
263    {ok, OtherNode} = start_node(Config, "-proto_dist gen_tcp"),
264    Tester = self(),
265    Demonitorer = spawn(BusyChannelNode,
266                        fun () ->
267                                pong = net_adm:ping(OtherNode),
268                                Tester ! {self(), alive},
269                                receive
270                                    {Tester, monitor, Pid} ->
271                                        _Mon = erlang:monitor(process, Pid)
272                                end,
273                                receive after infinity -> ok end
274                        end),
275    receive {Demonitorer, alive} -> ok end,
276    Demonitoree = spawn_link(OtherNode,
277                             fun () ->
278                                     wait_until(fun () ->
279                                                        {monitored_by, MB1}
280                                                            = process_info(self(),
281                                                                           monitored_by),
282                                                        lists:member(Demonitorer, MB1)
283                                                end),
284                                     Tester ! {self(), monitored},
285                                     wait_until(fun () ->
286                                                        {monitored_by, MB2}
287                                                            = process_info(self(),
288                                                                           monitored_by),
289                                                        not lists:member(Demonitorer, MB2)
290                                                end),
291                                     Tester ! {self(), got_demonitorer_demonitor_signal}
292                             end),
293    Demonitorer ! {self(), monitor, Demonitoree},
294    receive {Demonitoree, monitored} -> ok end,
295    make_busy(BusyChannelNode, OtherNode, 1000),
296    exit(Demonitorer, tester_killed_me),
297    receive
298        {Demonitoree, got_demonitorer_demonitor_signal} ->
299            unlink(Demonitoree),
300            ok
301    after
302        BusyTime*2 ->
303            ct:fail(missing_demonitor_signal)
304    end,
305    stop_node(BusyChannelNode),
306    stop_node(OtherNode),
307    ok.
308
309busy_dist_down_signal(Config) when is_list(Config) ->
310    BusyTime = 1000,
311    {ok, BusyChannelNode} = start_node(Config),
312    {ok, OtherNode} = start_node(Config, "-proto_dist gen_tcp"),
313    Tester = self(),
314    Exiter = spawn(BusyChannelNode,
315                   fun () ->
316                           pong = net_adm:ping(OtherNode),
317                           Tester ! {self(), alive},
318                           receive after infinity -> ok end
319                   end),
320    receive {Exiter, alive} -> ok end,
321    Monitorer = spawn_link(OtherNode,
322                        fun () ->
323                                process_flag(trap_exit, true),
324                                Mon = erlang:monitor(process, Exiter),
325                                receive
326                                    {'DOWN', Mon, process, Exiter, Reason} ->
327                                        tester_killed_me = Reason,
328                                        Tester ! {self(), got_exiter_down_message};
329                                    Unexpected ->
330                                        exit({unexpected_message, Unexpected})
331                                end
332                         end),
333    make_busy(BusyChannelNode, OtherNode, 1000),
334    exit(Exiter, tester_killed_me),
335    receive
336        {Monitorer, got_exiter_down_message} ->
337            unlink(Monitorer),
338            ok
339    after
340        BusyTime*2 ->
341            ct:fail(missing_down_signal)
342    end,
343    stop_node(BusyChannelNode),
344    stop_node(OtherNode),
345    ok.
346
347busy_dist_spawn_reply_signal(Config) when is_list(Config) ->
348    BusyTime = 1000,
349    {ok, BusyChannelNode} = start_node(Config),
350    {ok, OtherNode} = start_node(Config, "-proto_dist gen_tcp"),
351    Tester = self(),
352    Spawner = spawn_link(OtherNode,
353                         fun () ->
354                                 pong = net_adm:ping(BusyChannelNode),
355                                 Tester ! {self(), ready},
356                                 receive {Tester, go} -> ok end,
357                                 ReqID = spawn_request(BusyChannelNode,
358                                                       fun () -> ok end,
359                                                       []),
360                                 receive
361                                     {spawn_reply, ReqID, Result, _Pid} ->
362                                         ok = Result,
363                                         Tester ! {self(), got_spawn_reply_message};
364                                     Unexpected ->
365                                         exit({unexpected_message, Unexpected})
366                                 end
367                         end),
368    receive {Spawner, ready} -> ok end,
369    make_busy(BusyChannelNode, OtherNode, 1000),
370    Spawner ! {self(), go},
371    receive
372        {Spawner, got_spawn_reply_message} ->
373            unlink(Spawner),
374            ok
375    after
376        BusyTime*2 ->
377            ct:fail(missing_spawn_reply_signal)
378    end,
379    stop_node(BusyChannelNode),
380    stop_node(OtherNode),
381    ok.
382
383-record(erl_link, {type,           % process | port | dist_process
384                   pid = [],
385                   state,          % linked | unlinking
386                   id}).
387
388busy_dist_unlink_ack_signal(Config) when is_list(Config) ->
389    BusyTime = 1000,
390    {ok, BusyChannelNode} = start_node(Config),
391    {ok, OtherNode} = start_node(Config, "-proto_dist gen_tcp"),
392    Tester = self(),
393    Unlinkee = spawn(BusyChannelNode,
394                     fun () ->
395                             pong = net_adm:ping(OtherNode),
396                             Tester ! {self(), alive},
397                             receive after infinity -> ok end
398                     end),
399    receive {Unlinkee, alive} -> ok end,
400    Unlinker = spawn_link(OtherNode,
401                          fun () ->
402                                  erts_debug:set_internal_state(available_internal_state, true),
403                                  link(Unlinkee),
404                                  #erl_link{type = dist_process,
405                                            pid = Unlinkee,
406                                            state = linked} = find_proc_link(self(),
407                                                                             Unlinkee),
408                                  Tester ! {self(), ready},
409                                  receive {Tester, go} -> ok end,
410                                  unlink(Unlinkee),
411                                  #erl_link{type = dist_process,
412                                            pid = Unlinkee,
413                                            state = unlinking} = find_proc_link(self(),
414                                                                                Unlinkee),
415                                  wait_until(fun () ->
416                                                     false == find_proc_link(self(),
417                                                                             Unlinkee)
418                                             end),
419                                  Tester ! {self(), got_unlink_ack_signal}
420                          end),
421    receive {Unlinker, ready} -> ok end,
422    make_busy(BusyChannelNode, OtherNode, 1000),
423    Unlinker ! {self(), go},
424    receive
425        {Unlinker, got_unlink_ack_signal} ->
426            unlink(Unlinker),
427            ok
428    after
429        BusyTime*2 ->
430            ct:fail(missing_unlink_ack_signal)
431    end,
432    stop_node(BusyChannelNode),
433    stop_node(OtherNode),
434    ok.
435
436%%
437%% -- Internal utils --------------------------------------------------------
438%%
439
440load_driver(Config, Driver) ->
441    DataDir = proplists:get_value(data_dir, Config),
442    case erl_ddll:load_driver(DataDir, Driver) of
443        ok ->
444            ok;
445        {error, Error} = Res ->
446            io:format("~s\n", [erl_ddll:format_error(Error)]),
447            Res
448    end.
449
450wait_until(Fun) ->
451    case (catch Fun()) of
452        true ->
453            ok;
454        _ ->
455            receive after 1 -> ok end,
456            wait_until(Fun)
457    end.
458
459find_proc_link(Pid, To) when is_pid(Pid), is_pid(To) ->
460    lists:keyfind(To,
461                  #erl_link.pid,
462                  erts_debug:get_internal_state({link_list, Pid})).
463
464make_busy(OnNode, ToNode, Time) ->
465    Parent = self(),
466    Fun = fun () ->
467                  Proxy = self(),
468                  Sspndr = spawn_link(
469                             ToNode,
470                             fun () ->
471                                     IC = find_gen_tcp_input_cntrlr(OnNode),
472                                     erlang:suspend_process(IC),
473                                     Proxy ! {self(), input_cntrlr_suspended},
474                                     receive
475                                         {Proxy, resume_input_cntrlr} ->
476                                             erlang:resume_process(IC)
477                                     end,
478                                     Proxy ! {self(), input_cntrlr_resumed}
479                             end),
480                  receive
481                      {Sspndr, input_cntrlr_suspended} ->
482                          ok
483                  end,
484                  Spammer = spawn_link(
485                              OnNode,
486                              fun () ->
487                                      spammed = spam(ToNode),
488                                      Proxy ! {self(), channel_busy},
489                                      receive
490                                      after Time -> ok
491                                      end,
492                                      Proxy ! {self(), timeout}
493                              end),
494                  receive
495                      {Spammer, channel_busy} ->
496                          Parent ! {self(), channel_busy}
497                  end,
498                  receive
499                      {Spammer, timeout} ->
500                          Sspndr ! {self(), resume_input_cntrlr}
501                  end,
502                  receive
503                      {Sspndr, input_cntrlr_resumed} ->
504                          ok
505                  end
506          end,
507    Proxy = spawn_link(Fun),
508    receive
509        {Proxy, channel_busy} ->
510            ok
511    end,
512    Proxy.
513
514find_gen_tcp_input_cntrlr(Node) when is_atom(Node) ->
515    case lists:keyfind(Node, 1, erlang:system_info(dist_ctrl)) of
516        {Node, DistCtrl} ->
517            find_gen_tcp_input_cntrlr(DistCtrl);
518        false ->
519            undefined
520    end;
521find_gen_tcp_input_cntrlr(DistCtrl) when is_pid(DistCtrl) ->
522    {links, LList} = process_info(DistCtrl, links),
523    try
524        lists:foreach(fun (Pid) ->
525                              case process_info(Pid, initial_call) of
526                                  {initial_call,
527                                   {gen_tcp_dist,dist_cntrlr_input_setup,3}} ->
528                                      throw({input_ctrlr, Pid});
529                                  _ ->
530                                      ok
531                              end
532                      end,
533                      LList),
534        undefined
535    catch
536        throw:{input_ctrlr, DistInputCtrlr} ->
537            DistInputCtrlr
538    end.
539
540spam(Node) ->
541    To = {'__a_name_hopefully_not_registered__', Node},
542    Data = lists:seq(1, 100),
543    spam(To, Data).
544
545spam(To, Data) ->
546    case erlang:send(To, Data, [nosuspend]) of
547        nosuspend ->
548            spammed;
549        _ ->
550            spam(To, Data)
551    end.
552
553repeat(_Fun, N) when is_integer(N), N =< 0 ->
554    ok;
555repeat(Fun, N) when is_integer(N)  ->
556    Fun(),
557    repeat(Fun, N-1).
558
559start_node(Config, Args) ->
560    Name = list_to_atom(atom_to_list(?MODULE)
561			++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))
562			++ "-" ++ integer_to_list(erlang:system_time(second))
563			++ "-" ++ integer_to_list(erlang:unique_integer([positive]))),
564    Pa = filename:dirname(code:which(?MODULE)),
565    test_server:start_node(Name, slave, [{args,  "-pa " ++ Pa ++ " " ++ Args}]).
566
567start_node(Config) ->
568    start_node(Config, "").
569
570stop_node(Node) ->
571    test_server:stop_node(Node).
572