<div dir="ltr">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.  <div><br></div><div>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.</div><div><br></div><div>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:</div><div><br></div><div>-------------------------------------------</div><div><div>handle_cast(accept, State=#state{}) -></div><div>  {Module, Function, Arguments} = State#state.on_accept_mfa,</div><div>  {ok, AcceptSocket} = gen_tcp:accept(State#state.socket),</div><div>  (State#state.create_acceptor_fun)(),</div><div><br></div><div>  Pid = spawn(Module, Function, [AcceptSocket | Arguments]),</div><div>  io:format(user, "Accept socket valid: ~s~n", [erlang:port_info(AcceptSocket) =/= undefined]),</div><div><br></div><div>  {noreply, State}.</div><div><br></div><div>--------------------------------------------</div><div><br></div><div>%% Tests</div><div>test_mfa(ListenSocket) -></div><div>  Mfa = {?MODULE, send_self_message, [self()]},</div><div>  State = #state{socket = ListenSocket, on_accept_mfa = Mfa, create_acceptor_fun = fun do_nothing/0},</div><div>  spawn_link(fun () -> tcp_listener_server:handle_cast(accept, State) end),</div><div><br></div><div>  {ok, _} = gen_tcp:connect({127,0,0,1}, ?test_port, []),</div><div><br></div><div>  receive</div><div>    success -> ?_assert(true);</div><div>    X -> ?_assert(X)</div><div>  after 1000 -></div><div>    ?_assert(false)</div><div>  end.</div><div><br></div><div>%% Stubs</div><div>do_nothing() -> ok.</div><div><br></div><div>send_self_message(AcceptSocket, TestPid) -></div><div>  case erlang:port_info(AcceptSocket) of</div><div>    undefined -> TestPid ! bad_accept_socket;</div><div>    _ -> TestPid ! success</div><div>  end.</div><div><br></div><div>------------------------------------------------</div></div><div><br></div><div>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.</div><div><br></div><div>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.</div><div><br></div><div>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.</div></div>