[erlang-bugs] R15B03 - Following a send_timeout on a socket, the socket will no longer be closed on process exit.
Paul Cager
paul.cager@REDACTED
Thu Feb 7 19:06:51 CET 2013
Erlang R15B03 (V5.9.3.1) on Linux (RHEL 6.3).
Normally if the connected process of a TCP socket exits, the socket is
closed. However if a socket has option send_timeout (*without*
send_timeout_close), and a send does indeed timeout, then the socket
is not closed when the connected process exits. It is also not closed
if you use gen_tcp:close(), erlang:port_close() etc.
This results in a "leaked" socket that becomes impossible to close
(unless the remote end closes, or the whole beam OS process exits).
To reproduce (on Linux)
=======================
1) Build the runtime with INET_DRV_DEBUG turned on in inet_drv.c.
2) In one window run netstat so you can see which sockets are open
on the test port (1616)
while true; do netstat -nap | grep :1616 ; sleep 1; done
3) Run the Erlang test module below to demonstrate the *working*
case (i.e. not hitting a send_timeout).
4) Note from the debug output that tcp_inet_stop of inet_drv is
called. As a result the socket closes (indicated by netstat output).
5) Edit the test module to comment out the exit() where indicated
(%% COMMENT OUT FOLLOWING LINES TO DEMONSTRATE PROBLEM). Re-run the
test.
6) Note that tcp_inet_stop is *not* called and that the socket
remains established even after termination of the connected process.
7) If you replace the exit() following the send_timeout with a call
to prim_inet:close() or erlang:port_close() you get similar results.
============================================================================================
-module(test_tcp_close2).
-compile(export_all).
main() ->
{Pid, _Ref} = spawn_monitor(fun() -> run_test() end),
receive
{'DOWN', _, process, Pid, _} ->
io:format("Child process terminated - socket
should definately be closed~n", []),
io:format("All ports: ~p~n", [erlang:ports()])
end.
run_test() ->
{ok, LSock} = gen_tcp:listen(1616, [binary, {packet,0},
{active, false}, {reuseaddr, true}]),
spawn(?MODULE, connect_and_idle, [1616]),
{ok, Sock} = gen_tcp:accept(LSock),
{ok, FD} = prim_inet:getfd(Sock),
io:format("All ports: ~p~n", [erlang:ports()]),
io:format("** Accepted a connection with FD~p~n", [FD]),
debug_fd(FD),
ok = inet:setopts(Sock, [
{send_timeout, 250},
{active, once}, {nodelay, true}, {sndbuf, 32 * 1024},
{keepalive, true}
]),
fill_write(Sock, FD).
fill_write(Sock, FD) ->
%% Make sure the output buffer becomes full so we trigger send_timeout.
Size = 16 * 1024 * 8, %% 16 KB (k *bytes*).
case gen_tcp:send(Sock, <<0:Size>>) of
{error, timeout} ->
io:format("** timeout~n", []),
debug_fd(FD),
io:format("Self=~p, port_info=~p~n", [self(),
erlang:port_info(Sock)]),
io:format("** About to exit~n", []),
io:format("All ports: ~p~n", [erlang:ports()]),
exit(normal),
ok;
{error, X} ->
io:format("** Unexpected send error: ~p~n", [X]),
debug_fd(FD),
io:format("** port_info: ~p~n", [catch
erlang:port_info(Sock, connected)]);
ok ->
timer:sleep(1000),
debug_fd(FD),
io:format("Self=~p, port_info=~p~n", [self(),
erlang:port_info(Sock)]),
%% COMMENT OUT FOLLOWING LINES TO DEMONSTRATE PROBLEM
% Exit before send_timeout happens -
this should work
exit(normal),
%% END COMMENT OUT FOLLOWING LINES TO
DEMONSTRATE PROBLEM
fill_write(Sock, FD)
end.
debug_mbox() ->
receive X -> io:format("Got message ~p~n", [X]), debug_mbox()
after 1000 -> ok
end.
debug_fd(FD) ->
File = "/proc/" ++ os:getpid() ++ "/fd/" ++ integer_to_list(FD),
Result = os:cmd("ls -l " ++ File ++ " 2>&1"),
io:format("FD ~p: ~p~n", [FD, Result]).
connect_and_idle(Port) ->
{ok, _Sock} = gen_tcp:connect("localhost", Port,
[{active,false}, {mode,binary}, {packet, 0}]),
timer:sleep(60000),
io:format("idle reader closing~n", []).
============================================================================================
Normal Exit of Process.
(gdb) bt
#0 tcp_inet_stop (e=0x95e580) at drivers/common/inet_drv.c:
#1 0x0000000000495b87 in terminate_port (prt=0x7ffff679ba98) at beam/io.c:1976
#2 0x0000000000498df8 in erts_do_exit_port (p=0x7ffff679ba98,
from=<value optimized out>, reason=<value optimized out>) at
beam/io.c:2203
#3 0x00000000004aa513 in doit_exit_link (lnk=0x9919f0,
vpcontext=<value optimized out>) at beam/erl_process.c:8611
#4 0x0000000000515057 in erts_sweep_links (root=<value optimized
out>, doit=0x4aa260 <doit_exit_link>, context=0x7ffff4952bf0) at
beam/erl_monitors.c:859
#5 0x00000000004b0a46 in continue_exit_process (p=0x98b8d0,
pix_lock=<value optimized out>) at beam/erl_process.c:9005
#6 0x00000000005399c7 in terminate_proc (c_p=0x98b8d0, pc=<value
optimized out>, reg=<value optimized out>, bf=<value optimized out>)
at beam/beam_emu.c:5524
#7 handle_error (c_p=0x98b8d0, pc=<value optimized out>, reg=<value
optimized out>, bf=<value optimized out>) at beam/beam_emu.c:5399
#8 0x000000000053a6f4 in process_main () at beam/beam_emu.c:2436
#9 0x00000000004b17a9 in sched_thread_func (vesdp=0x7ffff7e5bd40) at
beam/erl_process.c:5217
#10 0x00000000005bbf86 in thr_wrapper (vtwd=0x7fffffffd850) at
pthread/ethread.c:106
#11 0x00000031b4407851 in start_thread () from /lib64/libpthread.so.0
#12 0x00000031b3ce811d in clone () from /lib64/libc.so.6
More information about the erlang-bugs
mailing list