Skip to content
Permalink
Browse files
Use {shutdown, Error} when terminating connection processes
Previously they exited `normal`, which helped avoid error log spam. However,
that also meant any linked helper processes would not exit when the main
connection process had been terminated.

To automatically clean up any linked processes, and continue avoiding
generating error logs, we can use `{shutdown, Error}` as the exit reason. That
error, along with `shutdown` atom, are special exit reasons which are
considered `normal` for proc_lib processes and will not generate error logs
[1].

Another benefit is having more specific exit reasons (send error, recv error,
etc.), which may help with debugging.

[1] https://www.erlang.org/docs/24/man/proc_lib.html#description
  • Loading branch information
nickva committed May 2, 2022
1 parent c724c3a commit e56a4dce6b360c5c5d037e8de33dd267790092e4
Showing 5 changed files with 40 additions and 40 deletions.
@@ -55,7 +55,7 @@ init(Server, Listen, Loop, Opts) ->
{error, Err}
when Err =:= closed orelse
Err =:= esslaccept orelse Err =:= timeout ->
exit(normal);
exit({shutdown, Err});
Other ->
%% Mitigate out of file descriptor scenario by sleeping for a
%% short time to slow error rate
@@ -116,14 +116,14 @@ request(Socket, Opts, Body) ->
{Protocol, _, {http_error, "\n"}}
when Protocol == http orelse Protocol == ssl ->
request(Socket, Opts, Body);
{tcp_closed, _} ->
mochiweb_socket:close(Socket), exit(normal);
{tcp_closed = Error, _} ->
mochiweb_socket:close(Socket), exit({shutdown, Error});
{tcp_error, _, emsgsize} = Other ->
handle_invalid_msg_request(Other, Socket, Opts);
{ssl_closed, _} ->
mochiweb_socket:close(Socket), exit(normal)
{ssl_closed = Error, _} ->
mochiweb_socket:close(Socket), exit({shutdown, Error})
after ?REQUEST_RECV_TIMEOUT ->
mochiweb_socket:close(Socket), exit(normal)
mochiweb_socket:close(Socket), exit({shutdown, request_recv_timeout})
end.

reentry(Body) ->
@@ -153,13 +153,13 @@ headers(Socket, Opts, Request, Headers, Body,
when Protocol == http orelse Protocol == ssl ->
headers(Socket, Opts, Request,
[{Name, Value} | Headers], Body, 1 + HeaderCount);
{tcp_closed, _} ->
mochiweb_socket:close(Socket), exit(normal);
{tcp_closed = Error, _} ->
mochiweb_socket:close(Socket), exit({shutdown, Error});
{tcp_error, _, emsgsize} = Other ->
handle_invalid_msg_request(Other, Socket, Opts, Request,
Headers)
after ?HEADERS_RECV_TIMEOUT ->
mochiweb_socket:close(Socket), exit(normal)
mochiweb_socket:close(Socket), exit({shutdown, headers_recv_timeout})
end.

call_body({M, F, A}, Req) when is_atom(M) ->
@@ -183,7 +183,7 @@ handle_invalid_msg_request(Msg, Socket, Opts, Request,
{{tcp_error, _, emsgsize}, true} ->
%% R15B02 returns this then closes the socket, so close and exit
mochiweb_socket:close(Socket),
exit(normal);
exit({shutdown, {tcp_error, emsgsize}});
_ ->
handle_invalid_request(Socket, Opts, Request,
RevHeaders)
@@ -198,7 +198,7 @@ handle_invalid_request(Socket, Opts, Request,
RevHeaders),
ReqM:respond({400, [], []}, Req),
mochiweb_socket:close(Socket),
exit(normal).
exit({shutdown, invalid_request}).

new_request(Socket, Opts, Request, RevHeaders) ->
ok =
@@ -211,7 +211,7 @@ new_request(Socket, Opts, Request, RevHeaders) ->
after_response(Body, {ReqM, _} = Req) ->
Socket = ReqM:get(socket, Req),
case ReqM:should_close(Req) of
true -> mochiweb_socket:close(Socket), exit(normal);
true -> mochiweb_socket:close(Socket), exit({shutdown, should_close});
false ->
ReqM:cleanup(Req),
erlang:garbage_collect(),
@@ -201,7 +201,7 @@ get(peer,
string:strip(lists:last(string:tokens(Hosts, ",")))
end;
{ok, {Addr, _Port}} -> inet_parse:ntoa(Addr);
{error, enotconn} -> exit(normal)
{error, enotconn = Error} -> exit({shutdown, Error})
end;
get(path,
{?MODULE,
@@ -261,7 +261,7 @@ send(Data,
_Headers]}) ->
case mochiweb_socket:send(Socket, Data) of
ok -> ok;
_ -> exit(normal)
_ -> exit({shutdown, send_error})
end.

%% @spec recv(integer(), request()) -> binary()
@@ -283,7 +283,7 @@ recv(Length, Timeout,
_Headers]}) ->
case mochiweb_socket:recv(Socket, Length, Timeout) of
{ok, Data} -> put(?SAVE_RECV, true), Data;
_ -> exit(normal)
_ -> exit({shutdown, recv_error})
end.

%% @spec body_length(request()) -> undefined | chunked | unknown_transfer_encoding | integer()
@@ -774,7 +774,7 @@ read_chunk_length({?MODULE,
{Hex, _Rest} = lists:splitwith(Splitter,
binary_to_list(Header)),
mochihex:to_int(Hex);
_ -> exit(normal)
_ -> exit({shutdown, read_chunk_length_recv_error})
end.

%% @spec read_chunk(integer(), request()) -> Chunk::binary() | [Footer::binary()]
@@ -792,7 +792,7 @@ read_chunk(0,
case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
{ok, <<"\r\n">>} -> Acc;
{ok, Footer} -> F1(F1, [Footer | Acc]);
_ -> exit(normal)
_ -> exit({shutdown, read_chunk_recv_error})
end
end,
Footers = F(F, []),
@@ -810,7 +810,7 @@ read_chunk(Length,
?IDLE_TIMEOUT)
of
{ok, <<Chunk:Length/binary, "\r\n">>} -> Chunk;
_ -> exit(normal)
_ -> exit({shutdown, read_chunk_recv_error})
end.

read_sub_chunks(Length, MaxChunkSize, Fun, FunState,
@@ -174,9 +174,9 @@ type({ssl, _}) ->
type(_) ->
plain.

exit_if_closed({error, closed}) ->
exit(normal);
exit_if_closed({error, einval}) ->
exit(normal);
exit_if_closed({error, closed = Error}) ->
exit({shutdown, Error});
exit_if_closed({error, einval = Error}) ->
exit({shutdown, Error});
exit_if_closed(Res) ->
Res.
@@ -48,23 +48,23 @@ loop(Socket, Body, State, WsVersion, ReplyChannel) ->

request(Socket, Body, State, WsVersion, ReplyChannel) ->
receive
{tcp_closed, _} ->
mochiweb_socket:close(Socket), exit(normal);
{ssl_closed, _} ->
mochiweb_socket:close(Socket), exit(normal);
{tcp_error, _, _} ->
mochiweb_socket:close(Socket), exit(normal);
{tcp_closed = Error, _} ->
mochiweb_socket:close(Socket), exit({shutdown, Error});
{ssl_closed = Error, _} ->
mochiweb_socket:close(Socket), exit({shutdown, Error});
{tcp_error, _, Error} ->
mochiweb_socket:close(Socket), exit({shutdown, {tcp_error, Error}});
{Proto, _, WsFrames}
when Proto =:= tcp orelse Proto =:= ssl ->
case parse_frames(WsVersion, WsFrames, Socket) of
close -> mochiweb_socket:close(Socket), exit(normal);
error -> mochiweb_socket:close(Socket), exit(normal);
close -> mochiweb_socket:close(Socket), exit({shutdown, websocket_parse_frames_close});
error -> mochiweb_socket:close(Socket), exit({shutdown, websocket_parse_frames_error});
Payload ->
NewState = call_body(Body, Payload, State,
ReplyChannel),
loop(Socket, Body, NewState, WsVersion, ReplyChannel)
end;
_ -> mochiweb_socket:close(Socket), exit(normal)
_ -> mochiweb_socket:close(Socket), exit({shutdown, websocket_request_error})
end.

call_body({M, F, A}, Payload, State, ReplyChannel) ->
@@ -96,7 +96,7 @@ upgrade_connection({ReqM, _} = Req, Body) ->
{Reentry, ReplyChannel};
_ ->
mochiweb_socket:close(ReqM:get(socket, Req)),
exit(normal)
exit({shutdown, websocket_handshake_error})
end.

make_handshake({ReqM, _} = Req) ->
@@ -204,19 +204,19 @@ parse_hybi_frames(Socket,
{active,
once}])),
receive
{tcp_closed, _} ->
mochiweb_socket:close(Socket), exit(normal);
{ssl_closed, _} ->
mochiweb_socket:close(Socket), exit(normal);
{tcp_error, _, _} ->
mochiweb_socket:close(Socket), exit(normal);
{tcp_closed = Error, _} ->
mochiweb_socket:close(Socket), exit({shutdown, Error});
{ssl_closed = Error, _} ->
mochiweb_socket:close(Socket), exit({shutdown, Error});
{tcp_error, _, Error} ->
mochiweb_socket:close(Socket), exit({shutdown, {tcp_error, Error}});
{Proto, _, Continuation}
when Proto =:= tcp orelse Proto =:= ssl ->
parse_hybi_frames(Socket,
<<PartFrame/binary, Continuation/binary>>, Acc);
_ -> mochiweb_socket:close(Socket), exit(normal)
_ -> mochiweb_socket:close(Socket), exit({shutdown, parse_hybi_frames_error})
after 5000 ->
mochiweb_socket:close(Socket), exit(normal)
mochiweb_socket:close(Socket), exit({shutdown, parse_hybi_frames_timeout})
end;
parse_hybi_frames(S,
<<_Fin:1, _Rsv:3, Opcode:4, _Mask:1, 127:7, 0:1,

0 comments on commit e56a4dc

Please sign in to comment.