diff options
author | Rickard Green <rickard@erlang.org> | 2022-07-12 16:38:17 +0200 |
---|---|---|
committer | Rickard Green <rickard@erlang.org> | 2022-07-12 17:57:59 +0200 |
commit | 04c913d6ce9eb8e9aed0e4b9f8c3d026861530e9 (patch) | |
tree | 35a31ae9d896a27373894df1c3e7b4e2d0dcddfd | |
parent | 3002f55f409f44122d3a45ac53bfd453e9aa2cb2 (diff) | |
download | erlang-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.c | 12 | ||||
-rw-r--r-- | erts/emulator/beam/io.c | 7 | ||||
-rw-r--r-- | erts/emulator/test/signal_SUITE.erl | 109 |
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)) |