aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/src/os.erl
diff options
context:
space:
mode:
authorLukas Larsson <[email protected]>2017-02-21 10:34:13 +0100
committerLukas Larsson <[email protected]>2017-02-21 13:34:21 +0100
commitbf9f79e81530b37d5ac4abe1494f270986d92cb4 (patch)
tree53787fece832c1460a274da79c669f6e6bc379e3 /lib/kernel/src/os.erl
parent8f0cfd160505e5ac6c100db7d1294c2e63dc0cbb (diff)
downloadotp-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.
Diffstat (limited to 'lib/kernel/src/os.erl')
-rw-r--r--lib/kernel/src/os.erl24
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.