summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRickard Green <rickard@erlang.org>2022-07-12 16:38:17 +0200
committerRickard Green <rickard@erlang.org>2022-07-12 17:57:59 +0200
commit04c913d6ce9eb8e9aed0e4b9f8c3d026861530e9 (patch)
tree35a31ae9d896a27373894df1c3e7b4e2d0dcddfd
parent3002f55f409f44122d3a45ac53bfd453e9aa2cb2 (diff)
downloaderlang-04c913d6ce9eb8e9aed0e4b9f8c3d026861530e9.tar.gz
[erts] Don't send exit signal due to a link with ongoing unlink op
-rw-r--r--erts/emulator/beam/erl_process.c12
-rw-r--r--erts/emulator/beam/io.c7
-rw-r--r--erts/emulator/test/signal_SUITE.erl109
3 files changed, 122 insertions, 6 deletions
diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c
index 65a6e1c6ad..b18338fbd6 100644
--- a/erts/emulator/beam/erl_process.c
+++ b/erts/emulator/beam/erl_process.c
@@ -13323,6 +13323,8 @@ erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds)
switch (lnk->type) {
case ERTS_LNK_TYPE_PROC:
ASSERT(is_internal_pid(lnk->other.item));
+ if (((ErtsILink *) lnk)->unlinking)
+ break;
erts_proc_sig_send_link_exit(c_p, c_p->common.id, lnk,
reason, SEQ_TRACE_TOKEN(c_p));
lnk = NULL;
@@ -13330,6 +13332,8 @@ erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds)
case ERTS_LNK_TYPE_PORT: {
Port *prt;
ASSERT(is_internal_port(lnk->other.item));
+ if (((ErtsILink *) lnk)->unlinking)
+ break;
prt = erts_port_lookup(lnk->other.item,
ERTS_PORT_SFLGS_INVALID_LOOKUP);
if (prt)
@@ -13349,8 +13353,14 @@ erts_proc_exit_handle_link(ErtsLink *lnk, void *vctxt, Sint reds)
ErtsDSigSendContext ctx;
int code;
+ dlnk = erts_link_to_other(lnk, &elnk);
+ if (elnk->unlinking) {
+ if (!erts_link_dist_delete(dlnk))
+ elnk = NULL;
+ break;
+ }
+
if (is_immed(reason)) {
- dlnk = erts_link_to_other(lnk, &elnk);
dist = elnk->dist;
ASSERT(is_external_pid(lnk->other.item));
diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c
index d2c6dffa53..8381a01081 100644
--- a/erts/emulator/beam/io.c
+++ b/erts/emulator/beam/io.c
@@ -3792,8 +3792,11 @@ typedef struct {
static int link_port_exit(ErtsLink *lnk, void *vpectxt, Sint reds)
{
ErtsPortExitContext *pectxt = vpectxt;
- erts_proc_sig_send_link_exit(NULL, pectxt->port_id,
- lnk, pectxt->reason, NIL);
+ if (((ErtsILink *) lnk)->unlinking)
+ erts_link_release(lnk);
+ else
+ erts_proc_sig_send_link_exit(NULL, pectxt->port_id,
+ lnk, pectxt->reason, NIL);
return 1;
}
diff --git a/erts/emulator/test/signal_SUITE.erl b/erts/emulator/test/signal_SUITE.erl
index 4981d48621..e307ac7adf 100644
--- a/erts/emulator/test/signal_SUITE.erl
+++ b/erts/emulator/test/signal_SUITE.erl
@@ -35,7 +35,8 @@
% Test cases
-export([xm_sig_order/1,
- kill2killed/1]).
+ kill2killed/1,
+ unlink_exit/1]).
init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
[{testcase, Func}|Config].
@@ -55,7 +56,8 @@ suite() ->
all() ->
[xm_sig_order,
- kill2killed].
+ kill2killed,
+ unlink_exit].
%% Test that exit signals and messages are received in correct order
@@ -66,7 +68,6 @@ xm_sig_order(Config) when is_list(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),
@@ -152,6 +153,102 @@ spawn_link_line(NodeA, NodeB, Type, N, Tester) ->
receive after infinity -> ok end
end).
+
+unlink_exit(Config) when is_list(Config) ->
+ %% OTP-18177
+ %%
+ %% This bug is theoretically possible, at least in the
+ %% node local scenario, but more or less undetectable and
+ %% quite harmless when it hits. A process A (the child in
+ %% the testcase) could get actual exit reason of another
+ %% process B (the parent in the testcase) when it should
+ %% have gotten 'noproc' as exit reason. This can happen if
+ %% 1. B unlinks A
+ %% 2. B begin terminating before it has received an unlink
+ %% ack from A
+ %% 3. A links to B after it has received the unlink signal
+ %%
+ %% This testcase hammers on the above scenario, but I have
+ %% not seen it fail yet though when the bug is present...
+ repeat(fun unlink_exit_test/0, 1000).
+
+unlink_exit_test() ->
+ Tester = self(),
+ ChildFun =
+ fun () ->
+ process_flag(trap_exit, true),
+ Tester ! {child, self()},
+ Parent = receive {tester_parent, Tester, Pid} -> Pid end,
+ Parent ! {go, self()},
+ busy_wait_until(fun () ->
+ receive {go, Parent} -> true
+ after 0 -> false
+ end
+ end),
+ IsAlive = erlang:is_process_alive(Parent),
+ try
+ link(Parent),
+ case IsAlive of
+ false ->
+ receive
+ {'EXIT', Parent, noproc} ->
+ exit(ok);
+ {'EXIT', Parent, R1} ->
+ exit({not_alive_unexpected_exit_reason, R1})
+ after 1000 ->
+ exit(not_alive_missing_exit)
+ end;
+ true ->
+ receive
+ {'EXIT', Parent, R2} when R2 == noproc;
+ R2 == bye ->
+ exit(ok);
+ {'EXIT', Parent, R2} ->
+ exit({alive_unexpected_exit_reason, R2})
+ after 1000 ->
+ exit(alive_missing_exit)
+ end
+ end
+ catch error:noproc ->
+ receive
+ {'EXIT', Parent, _} = X0 ->
+ exit({unexpected_exit, X0})
+ after 1000 ->
+ exit(ok)
+ end
+ end
+ end,
+ {Parent, PMon} = spawn_opt(fun () ->
+ %% Work to do when terminating in order
+ %% to increase the likelyhood of the
+ %% bug triggering (if present)...
+ T = ets:new(x,[]),
+ ets:insert(T, lists:map(fun (I) ->
+ {I,I}
+ end,
+ lists:seq(1,10000))),
+
+ Child = spawn_opt(ChildFun,
+ [{priority, high},
+ link]),
+ receive {go, Child} -> ok end,
+ unlink(Child),
+ Child ! {go, self()},
+ exit(bye)
+ end, [{priority, high}, monitor]),
+ Child = receive {child, Chld} -> Chld end,
+ CMon = erlang:monitor(process, Child),
+ Child ! {tester_parent, Tester, Parent},
+ receive
+ {'DOWN', PMon, process, Parent, bye} ->
+ ok
+ end,
+ receive
+ {'DOWN', CMon, process, Child, ok} ->
+ ok;
+ {'DOWN', CMon, process, Child, ChildReason} ->
+ ct:fail(ChildReason)
+ end.
%%
%% -- Internal utils --------------------------------------------------------
@@ -163,6 +260,12 @@ repeat(Fun, N) when is_integer(N) ->
Fun(),
repeat(Fun, N-1).
+busy_wait_until(Fun) ->
+ case catch Fun() of
+ true -> ok;
+ _ -> busy_wait_until(Fun)
+ end.
+
start_node(Config) ->
Name = list_to_atom(atom_to_list(?MODULE)
++ "-" ++ atom_to_list(proplists:get_value(testcase, Config))