diff options
author | Lukas Larsson <[email protected]> | 2017-02-21 10:34:13 +0100 |
---|---|---|
committer | Lukas Larsson <[email protected]> | 2017-02-21 13:34:21 +0100 |
commit | bf9f79e81530b37d5ac4abe1494f270986d92cb4 (patch) | |
tree | 53787fece832c1460a274da79c669f6e6bc379e3 | |
parent | 8f0cfd160505e5ac6c100db7d1294c2e63dc0cbb (diff) | |
download | otp-bf9f79e81530b37d5ac4abe1494f270986d92cb4.tar.gz otp-bf9f79e81530b37d5ac4abe1494f270986d92cb4.tar.bz2 otp-bf9f79e81530b37d5ac4abe1494f270986d92cb4.zip |
kernel: Fix hanging os:cmd race condition
If the port terminates before Port ! close is issued,
there will be no 'closed' reply, so the old code would
hang. As it turns out there is no way to figure out
if a closed reply is supposed to come as that reply
is sent after all links and monitors are triggered.
So we have to use the synchronous port_close to close
the port.
-rw-r--r-- | lib/kernel/src/os.erl | 24 |
1 files changed, 15 insertions, 9 deletions
diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl index f8519d3a5e..03d4324992 100644 --- a/lib/kernel/src/os.erl +++ b/lib/kernel/src/os.erl @@ -289,12 +289,11 @@ get_data(Port, MonRef, Eot, Sofar) -> more -> get_data(Port, MonRef, Eot, [Sofar,Bytes]); Last -> - Port ! {self(), close}, - flush_until_closed(Port), - flush_exit(Port), + catch port_close(Port), + flush_until_down(Port, MonRef), iolist_to_binary([Sofar, Last]) end; - {'DOWN', MonRef, _, _ , _} -> + {'DOWN', MonRef, _, _, _} -> flush_exit(Port), iolist_to_binary(Sofar) end. @@ -308,18 +307,25 @@ eot(Bs, Eot) -> binary:part(Bs,{0, Pos}) end. -flush_until_closed(Port) -> +%% When port_close returns we know that all the +%% messages sent have been sent and that the +%% DOWN message is after them all. +flush_until_down(Port, MonRef) -> receive {Port, {data, _Bytes}} -> - flush_until_closed(Port); - {Port, closed} -> - true + flush_until_down(Port, MonRef); + {'DOWN', MonRef, _, _, _} -> + flush_exit(Port) end. +%% The exit signal is always delivered before +%% the down signal, so we can be sure that if there +%% was an exit message sent, it will be in the +%% mailbox now. flush_exit(Port) -> receive {'EXIT', Port, _} -> ok - after 1 -> % force context switch + after 0 -> ok end. |