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
diff --git a/src/mochiweb_acceptor.erl b/src/mochiweb_acceptor.erl
index 2fed6bd..8bad29e 100644
--- a/src/mochiweb_acceptor.erl
+++ b/src/mochiweb_acceptor.erl
@@ -55,7 +55,7 @@
       {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
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index aff95aa..dc9364f 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -116,14 +116,14 @@
       {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 @@
 	  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 @@
       {{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 @@
 				  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 @@
 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(),
diff --git a/src/mochiweb_request.erl b/src/mochiweb_request.erl
index a1d2d6e..c4fcff0 100644
--- a/src/mochiweb_request.erl
+++ b/src/mochiweb_request.erl
@@ -201,7 +201,7 @@
 		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 @@
        _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 @@
        _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 @@
 	  {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 @@
 		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 @@
 			      ?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,
diff --git a/src/mochiweb_socket.erl b/src/mochiweb_socket.erl
index 7408469..8ac24f5 100644
--- a/src/mochiweb_socket.erl
+++ b/src/mochiweb_socket.erl
@@ -174,9 +174,9 @@
 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.
diff --git a/src/mochiweb_websocket.erl b/src/mochiweb_websocket.erl
index c2e2ab6..22ef924 100644
--- a/src/mochiweb_websocket.erl
+++ b/src/mochiweb_websocket.erl
@@ -48,23 +48,23 @@
 
 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 @@
 	  {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 @@
 								{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,