[erlang-questions] Why does adding an io:format call make my accept socket remain valid?

Matthew Shapiro <>
Sun Mar 27 06:16:16 CEST 2016


In my attempts at learning Erlang I am currently writing a generic
tcp_listener application library.  One thing I want is for the library to
be decoupled from the main project I am writing (simple IRC server) and
have it be testable.

The way I am handling that is by passing in a MFA for it to pass the
accepted socket to, so the tcp_listener gen_server can go back to handling
more sockets.

I am testing this by creating an EUnit test that connects to the listening
socket passed to the listener server and passing in a controlled MFA that
sends itself a message back.  The code is as follows:

-------------------------------------------
handle_cast(accept, State=#state{}) ->
  {Module, Function, Arguments} = State#state.on_accept_mfa,
  {ok, AcceptSocket} = gen_tcp:accept(State#state.socket),
  (State#state.create_acceptor_fun)(),

  Pid = spawn(Module, Function, [AcceptSocket | Arguments]),
  io:format(user, "Accept socket valid: ~s~n",
[erlang:port_info(AcceptSocket) =/= undefined]),

  {noreply, State}.

--------------------------------------------

%% Tests
test_mfa(ListenSocket) ->
  Mfa = {?MODULE, send_self_message, [self()]},
  State = #state{socket = ListenSocket, on_accept_mfa = Mfa,
create_acceptor_fun = fun do_nothing/0},
  spawn_link(fun () -> tcp_listener_server:handle_cast(accept, State) end),

  {ok, _} = gen_tcp:connect({127,0,0,1}, ?test_port, []),

  receive
    success -> ?_assert(true);
    X -> ?_assert(X)
  after 1000 ->
    ?_assert(false)
  end.

%% Stubs
do_nothing() -> ok.

send_self_message(AcceptSocket, TestPid) ->
  case erlang:port_info(AcceptSocket) of
    undefined -> TestPid ! bad_accept_socket;
    _ -> TestPid ! success
  end.

------------------------------------------------

Now I am 100% sure that my handle_cast is wrong, in that I should be
passing ownership of the accepted socket to the spawned process.  Adding
that makes everything work honkey dorey.

Before I realized that though, I got stuck for quite a while with a failing
test that I couldn't figure out why.  Before I added that io:format call in
handle_cast/2 my tests were failing with erlang:port_info/1 returning
undefined.  I was going crazy in the shell trying to figure out why and
adding debug statements all over the place when I added the io:format/3
call you see now.  Suddenly everything works and the test passes.

Why does that io:format/3 call (only when after the spawn/3 call) keep the
socket open and active for the spawned process?  I wouldn't expect it to be
garbage collected because the spawned process holds a reference to it (and
even adding it to the State in the return doesn't fix it).  Only other
thing I could think of was that handle_cast is run in it's own process
which closes the socket when it dies (since it has ownership), but that
doesn't make sense from what I've read about OTP.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20160327/6bb8be56/attachment.html>


More information about the erlang-questions mailing list