Skip to content

Commit 77e4eea

Browse files
committed
Fix possible race condition in port:call/2,3
Also fix bug in gen_* that hung if underlying socket was closed, by factorizing logic and invoking port:call/2 This bug created issues with esp32's socket_driver implementation that terminates the process when the socket is closed from the other end Also fix semantics of gen_*:close() that should always return ok. Also update test_port test to actually test with a port as port module no longer can be used with non-ports. Signed-off-by: Paul Guyot <[email protected]>
1 parent f8705c9 commit 77e4eea

File tree

6 files changed

+69
-91
lines changed

6 files changed

+69
-91
lines changed

libs/eavmlib/src/port.erl

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,26 @@
2424
-export([call/2, call/3]).
2525

2626
-spec call(pid(), Message :: term()) -> term().
27-
call(Pid, Message) ->
28-
case erlang:is_process_alive(Pid) of
29-
false ->
30-
{error, noproc};
31-
true ->
32-
Ref = erlang:make_ref(),
33-
Pid ! {self(), Ref, Message},
34-
receive
35-
out_of_memory -> out_of_memory;
36-
{Ref, Reply} -> Reply
37-
end
38-
end.
27+
call(Port, Message) ->
28+
call(Port, Message, infinity).
3929

4030
-spec call(pid(), Message :: term(), TimeoutMs :: non_neg_integer()) -> term() | {error, timeout}.
41-
call(Pid, Message, TimeoutMs) ->
42-
case erlang:is_process_alive(Pid) of
43-
false ->
44-
{error, noproc};
45-
true ->
46-
Ref = erlang:make_ref(),
47-
Pid ! {self(), Ref, Message},
48-
receive
49-
out_of_memory -> out_of_memory;
50-
{Ref, Reply} -> Reply
51-
after TimeoutMs ->
52-
{error, timeout}
53-
end
54-
end.
31+
call(Port, Message, TimeoutMs) ->
32+
Ref = erlang:make_ref(),
33+
MonitorRef = monitor(port, Port),
34+
Port ! {self(), Ref, Message},
35+
Result =
36+
receive
37+
{'DOWN', MonitorRef, port, Port, normal} ->
38+
{error, noproc};
39+
{'DOWN', MonitorRef, port, Port, Reason} ->
40+
{error, Reason};
41+
out_of_memory ->
42+
out_of_memory;
43+
{Ref, Ret} ->
44+
Ret
45+
after TimeoutMs ->
46+
{error, timeout}
47+
end,
48+
demonitor(MonitorRef, [flush]),
49+
Result.

libs/estdlib/src/gen_tcp.erl

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -237,15 +237,6 @@ connect(DriverPid, Address, Port, Params) ->
237237
ErrorReason
238238
end.
239239

240-
%% @private
241-
call(DriverPid, Msg) ->
242-
Ref = erlang:make_ref(),
243-
DriverPid ! {self(), Ref, Msg},
244-
receive
245-
{Ref, Ret} ->
246-
Ret
247-
end.
248-
249240
%% TODO implement this in lists
250241

251242
%% @private
@@ -286,3 +277,14 @@ normalize_address({A, B, C, D}) when
286277
"." ++ integer_to_list(D).
287278

288279
%% TODO IPv6
280+
281+
%%
282+
%% Internal operations
283+
%%
284+
285+
call(Port, Msg) ->
286+
case port:call(Port, Msg) of
287+
{error, noproc} -> {error, closed};
288+
out_of_memory -> {error, enomem};
289+
Result -> Result
290+
end.

libs/estdlib/src/gen_udp.erl

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,13 @@ recv(Socket, Length, Timeout) ->
135135

136136
%%-----------------------------------------------------------------------------
137137
%% @param Socket the socket to close
138-
%% @returns ok, if closing the socket succeeded, or {error, Reason}, if
139-
%% closing the socket failed for any reason.
138+
%% @returns ok
140139
%% @doc Close the socket.
141140
%% @end
142141
%%-----------------------------------------------------------------------------
143-
-spec close(inet:socket()) -> ok | {error, Reason :: reason()}.
142+
-spec close(inet:socket()) -> ok.
144143
close(Socket) ->
145-
call(Socket, {close}).
144+
inet:close(Socket).
146145

147146
%%-----------------------------------------------------------------------------
148147
%% @param Socket the socket to which to assign the pid
@@ -179,15 +178,6 @@ init(DriverPid, PortNum, Params) ->
179178
ErrorReason
180179
end.
181180

182-
%% @private
183-
call(DriverPid, Msg) ->
184-
Ref = erlang:make_ref(),
185-
DriverPid ! {self(), Ref, Msg},
186-
receive
187-
{Ref, Ret} ->
188-
Ret
189-
end.
190-
191181
%% TODO implement this in lists
192182

193183
%% @private
@@ -209,3 +199,14 @@ merge(Config, [H | T], Accum) ->
209199
Value ->
210200
merge(Config, T, [{Key, Value} | Accum])
211201
end.
202+
203+
%%
204+
%% Internal operations
205+
%%
206+
207+
call(Port, Msg) ->
208+
case port:call(Port, Msg) of
209+
{error, noproc} -> {error, closed};
210+
out_of_memory -> {error, enomem};
211+
Result -> Result
212+
end.

libs/estdlib/src/inet.erl

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ port(Socket) ->
7070
%%-----------------------------------------------------------------------------
7171
-spec close(Socket :: socket()) -> ok.
7272
close(Socket) ->
73-
call(Socket, {close}).
73+
call(Socket, {close}),
74+
ok.
7475

7576
%%-----------------------------------------------------------------------------
7677
%% @param Socket the socket
@@ -98,11 +99,9 @@ peername(Socket) ->
9899
%% Internal operations
99100
%%
100101

101-
%% @private
102-
call(Socket, Msg) ->
103-
Ref = erlang:make_ref(),
104-
Socket ! {self(), Ref, Msg},
105-
receive
106-
{Ref, Ret} ->
107-
Ret
102+
call(Port, Msg) ->
103+
case port:call(Port, Msg) of
104+
{error, noproc} -> {error, einval};
105+
out_of_memory -> {error, enomem};
106+
Result -> Result
108107
end.

tests/libs/eavmlib/test_port.erl

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,44 +20,17 @@
2020

2121
-module(test_port).
2222

23-
-export([test/0, loop/0]).
23+
-export([test/0]).
2424

2525
test() ->
26-
{Pid, Ref} = spawn_opt(?MODULE, loop, [], [monitor]),
26+
ConsolePort = open_port({spawn, "console"}, []),
2727

28-
pong = port:call(Pid, ping),
29-
pong = port:call(Pid, ping, 1000),
30-
out_of_memory = port:call(Pid, out_of_memory),
31-
out_of_memory = port:call(Pid, out_of_memory, 1000),
32-
33-
{error, timeout} = port:call(Pid, {sleep, 500}, 250),
34-
35-
ok = port:call(Pid, halt),
28+
ok = port:call(ConsolePort, flush),
29+
ok = port:call(ConsolePort, flush, 1000),
30+
{error, badarg} = port:call(ConsolePort, unknown_cmd),
31+
ConsolePort ! {self(), close},
3632
receive
37-
{'DOWN', Ref, process, Pid, normal} -> ok
33+
{ConsolePort, closed} -> ok
3834
end,
39-
{error, noproc} = port:call(Pid, ping),
40-
{error, noproc} = port:call(Pid, ping, 1000),
41-
35+
{error, noproc} = port:call(ConsolePort, flush),
4236
ok.
43-
44-
loop() ->
45-
receive
46-
{Pid, Ref, ping} ->
47-
Pid ! {Ref, pong},
48-
loop();
49-
{Pid, _Ref, out_of_memory} ->
50-
Pid ! out_of_memory,
51-
loop();
52-
{Pid, Ref, {sleep, Ms}} ->
53-
receive
54-
after Ms -> ok
55-
end,
56-
Pid ! {Ref, ok},
57-
loop();
58-
{Pid, Ref, halt} ->
59-
Pid ! {Ref, ok};
60-
Garbage ->
61-
erlang:display({unexpected_message, Garbage}),
62-
loop()
63-
end.

tests/libs/estdlib/test_gen_tcp.erl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ test() ->
2828
ok = test_echo_server(),
2929
ok = test_echo_server(true),
3030
ok = test_listen_connect_parameters(),
31+
ok = test_tcp_double_close(),
3132
ok.
3233

3334
test_echo_server() ->
@@ -229,3 +230,10 @@ test_listen_connect_parameters_server_loop(ListenMode, false = ListenActive, Soc
229230
Other ->
230231
{error, {unexpected_result, server, Other}}
231232
end.
233+
234+
test_tcp_double_close() ->
235+
{ok, Socket} = gen_tcp:listen(10543, [{active, false}]),
236+
ok = gen_tcp:close(Socket),
237+
ok = gen_tcp:close(Socket),
238+
{error, closed} = gen_tcp:recv(Socket, 512, 5000),
239+
ok.

0 commit comments

Comments
 (0)