Copilot review (PR #4, commit ea95a0a, src/tcp_server.erl:224-232).
The listener sets process_flag(trap_exit, true) in init/1, and the handle_info({'EXIT', _Pid, _Reason}, State) clause matches any linked pid and unconditionally respawns an acceptor:
handle_info({'EXIT', _Pid, _Reason}, State) ->
%% Acceptor (linked) exited. Respawn it ...
spawn_link(fun() -> accept_loop(Self, ListenSocket) end),
{noreply, State};
Because the listener is started via start_link/3,4, it is also linked to its parent (supervisor/caller). If that parent (or any other linked process) exits, this clause keeps the server alive instead of terminating, and spawns a duplicate acceptor loop.
Fix: track the acceptor pid in listener_state and only respawn when the exited pid matches the current acceptor; let other linked exits propagate/terminate normally.
Validated against current code: still present.
Copilot review (PR #4, commit ea95a0a,
src/tcp_server.erl:224-232).The listener sets
process_flag(trap_exit, true)ininit/1, and thehandle_info({'EXIT', _Pid, _Reason}, State)clause matches any linked pid and unconditionally respawns an acceptor:Because the listener is started via
start_link/3,4, it is also linked to its parent (supervisor/caller). If that parent (or any other linked process) exits, this clause keeps the server alive instead of terminating, and spawns a duplicate acceptor loop.Fix: track the acceptor pid in
listener_stateand only respawn when the exited pid matches the current acceptor; let other linked exits propagate/terminate normally.Validated against current code: still present.