I ran into a strange problem. I'm testing the code found in the docs that creates several processes to accept incoming connections from a listen socket:
-module(servertest).
%% API
-export([start/2, server/1]).
start(Num,LPort) ->
case gen_tcp:listen(LPort,[{active, false},{packet,0}]) of
{ok, ListenSock} ->
start_servers(Num,ListenSock),
{ok, Port} = inet:port(ListenSock),
Port;
{error,Reason} ->
{error,Reason}
end.
start_servers(0,_) ->
ok;
start_servers(Num,LS) ->
spawn(?MODULE,server,[LS]),
start_servers(Num-1,LS).
server(LS) ->
io:format("server started~n", []),
case gen_tcp:accept(LS) of
{ok,S} ->
loop(S),
server(LS);
Other ->
io:format("accept returned ~w - goodbye!~n",[Other]),
ok
end.
loop(S) ->
inet:setopts(S,[{active,once}]),
receive
{tcp,S,Data} ->
Answer = Data, % Not implemented in this example
gen_tcp:send(S,Answer),
loop(S);
{tcp_closed,S} ->
io:format("Socket ~w closed [~w]~n",[S,self()]),
ok
end.
This works perfectly fine:
servertest:start(1, 1241).
server started
1241
The problem starts when I add another layer of spawn
and the code that creates the listen socket starts the accept servers has already been spawned:
-module(servertest2).
%% API
-export([start/2, server/1, start_listening/2]).
start(Num,LPort) ->
erlang:spawn_link(?MODULE, start_listening, [Num,LPort]).
start_listening(Num,LPort) ->
case gen_tcp:listen(LPort,[{active, false},{packet,0}]) of
{ok, ListenSock} ->
start_servers(Num,ListenSock),
{ok, Port} = inet:port(ListenSock),
Port;
{error,Reason} ->
{error,Reason}
end.
start_servers(0,_) ->
ok;
start_servers(Num,LS) ->
spawn(?MODULE,server,[LS]),
start_servers(Num-1,LS).
server(LS) ->
io:format("server started~n", []),
case gen_tcp:accept(LS) of
{ok,S} ->
loop(S),
server(LS);
Other ->
io:format("accept returned ~w - goodbye!~n",[Other]),
ok
end.
loop(S) ->
inet:setopts(S,[{active,once}]),
receive
{tcp,S,Data} ->
Answer = Data, % Not implemented in this example
gen_tcp:send(S,Answer),
loop(S);
{tcp_closed,S} ->
io:format("Socket ~w closed [~w]~n",[S,self()]),
ok
end.
When it's started like this, the accept
immediately (without any incoming connection) returns {error,closed}
:
servertest2:start(1, 1242).
server started
<0.13452.0>
accept returned {error,closed} - goodbye!
For clarify here's a diff between the two versions:
--- servertest.erl 2021-11-25 00:04:32.000000000 +0100
+++ servertest2.erl 2021-11-25 00:04:01.000000000 +0100
@@ -1,9 +1,12 @@
--module(servertest).
+-module(servertest2).
%% API
--export([start/2, server/1]).
+-export([start/2, server/1, start_listening/2]).
start(Num,LPort) ->
+ erlang:spawn_link(?MODULE, start_listening, [Num,LPort]).
+
+start_listening(Num,LPort) ->
case gen_tcp:listen(LPort,[{active, false},{packet,0}]) of
{ok, ListenSock} ->
start_servers(Num,ListenSock),
Thank you!
Without testing it, I'd say that in the second case the process that's the owner of the listen socket dies naturally after spawning the acceptors, thus closing the listen socket.
Add some timer:sleep
or receive
after calling start_servers
to verify it.