This is an automated email from the ASF dual-hosted git repository. vatamane pushed a commit to branch 3.3.x in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit c7ea3b88a1925153609a4cbed9d498d80f26f553 Author: Nick Vatamaniuc <vatam...@gmail.com> AuthorDate: Thu Aug 31 15:40:20 2023 -0400 Update mochiweb to 3.2.0 Mochiweb [3.2.0](https://github.com/mochi/mochiweb/releases/tag/v3.2.0) has a `mochiweb_request:is_closed/1` function which we can use instead of our socket check in `chttpd_util`. Since `mochiweb_request:is_closed/1` takes a request object, switch the client request monitoring code and tests to use requests insteads of sockets. That involves doing a bunch of renames mostly which makes the PR a bit long, but otherwise, everything should work as before. --- rebar.config.script | 2 +- src/chttpd/src/chttpd.erl | 4 +- src/chttpd/src/chttpd_util.erl | 120 +++++---------------------- src/chttpd/test/eunit/chttpd_util_test.erl | 33 ++++---- src/fabric/src/fabric_db_update_listener.erl | 16 ++-- src/fabric/src/fabric_streams.erl | 36 +++++--- src/fabric/src/fabric_view_changes.erl | 4 +- 7 files changed, 74 insertions(+), 141 deletions(-) diff --git a/rebar.config.script b/rebar.config.script index 1e3c5924b..9d9a6f6e7 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -154,7 +154,7 @@ DepDescs = [ {hyper, "hyper", {tag, "CouchDB-2.2.0-7"}}, {ibrowse, "ibrowse", {tag, "CouchDB-4.4.2-5"}}, {jiffy, "jiffy", {tag, "CouchDB-1.0.9-2"}}, -{mochiweb, "mochiweb", {tag, "v3.1.1"}}, +{mochiweb, "mochiweb", {tag, "v3.2.0"}}, {meck, "meck", {tag, "0.9.2"}}, {recon, "recon", {tag, "2.5.3"}} ]. diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl index f8088593f..e35f8e9b7 100644 --- a/src/chttpd/src/chttpd.erl +++ b/src/chttpd/src/chttpd.erl @@ -320,7 +320,7 @@ handle_request_int(MochiReq) -> erlang:put(dont_log_response, true), % Save client socket so that it can be monitored for disconnects - chttpd_util:mochiweb_socket_set(MochiReq:get(socket)), + chttpd_util:mochiweb_client_req_set(MochiReq), {HttpReq2, Response} = case before_request(HttpReq0) of @@ -330,7 +330,7 @@ handle_request_int(MochiReq) -> {HttpReq0, Response0} end, - chttpd_util:mochiweb_socket_clean(), + chttpd_util:mochiweb_client_req_clean(), {Status, Code, Reason, Resp} = split_response(Response), diff --git a/src/chttpd/src/chttpd_util.erl b/src/chttpd/src/chttpd_util.erl index 05adef717..4dc590949 100644 --- a/src/chttpd/src/chttpd_util.erl +++ b/src/chttpd/src/chttpd_util.erl @@ -23,14 +23,14 @@ get_chttpd_auth_config_boolean/2, maybe_add_csp_header/3, get_db_info/1, - mochiweb_socket_set/1, - mochiweb_socket_clean/0, - mochiweb_socket_get/0, - mochiweb_socket_check_msec/0, + mochiweb_client_req_set/1, + mochiweb_client_req_clean/0, + mochiweb_client_req_get/0, + mochiweb_client_req_check_msec/0, stop_client_process_if_disconnected/2 ]). --define(MOCHIWEB_SOCKET, mochiweb_connection_socket). +-define(MOCHIWEB_CLIENT_REQ, mochiweb_client_req). -define(DISCONNECT_CHECK_MSEC, 30000). -define(DISCONNECT_CHECK_JITTER_MSEC, 15000). @@ -120,16 +120,16 @@ get_db_info(DbName) -> _Tag:Error -> {error, Error} end. -mochiweb_socket_set(Sock) -> - put(?MOCHIWEB_SOCKET, Sock). +mochiweb_client_req_set(ClientReq) -> + put(?MOCHIWEB_CLIENT_REQ, ClientReq). -mochiweb_socket_clean() -> - erase(?MOCHIWEB_SOCKET). +mochiweb_client_req_clean() -> + erase(?MOCHIWEB_CLIENT_REQ). -mochiweb_socket_get() -> - get(?MOCHIWEB_SOCKET). +mochiweb_client_req_get() -> + get(?MOCHIWEB_CLIENT_REQ). -mochiweb_socket_check_msec() -> +mochiweb_client_req_check_msec() -> MSec = config:get_integer( "chttpd", "disconnect_check_msec", ?DISCONNECT_CHECK_MSEC ), @@ -138,98 +138,18 @@ mochiweb_socket_check_msec() -> ), max(100, MSec + rand:uniform(max(1, JitterMSec))). -stop_client_process_if_disconnected(Pid, Sock) -> - case is_mochiweb_socket_closed(Sock) of +stop_client_process_if_disconnected(_Pid, undefined) -> + ok; +stop_client_process_if_disconnected(Pid, ClientReq) -> + case mochiweb_request:is_closed(ClientReq) of true -> exit(Pid, {shutdown, client_disconnected}), couch_stats:increment_counter([couchdb, httpd, abandoned_streaming_requests]), ok; false -> - ok - end. - -is_mochiweb_socket_closed(undefined) -> - false; -is_mochiweb_socket_closed(Sock) -> - OsType = os:type(), - case tcp_info_opt(OsType) of - {raw, _, _, _} = InfoOpt -> - case mochiweb_socket:getopts(Sock, [InfoOpt]) of - {ok, [{raw, _, _, <<State:8/native, _/binary>>}]} -> - tcp_is_closed(State, OsType); - {ok, []} -> - false; - {error, einval} -> - % Already cleaned up - true; - {error, _} -> - false - end; + ok; undefined -> - false + % Treat unsupported OS-es (ex. Windows) as `not closed` + % so we default to the previous behavior. + ok end. - -% All OS-es have the tcpi_state (uint8) as first member of tcp_info struct - -tcp_info_opt({unix, linux}) -> - %% netinet/in.h - %% IPPROTO_TCP = 6 - %% - %% netinet/tcp.h - %% #define TCP_INFO 11 - %% - {raw, 6, 11, 1}; -tcp_info_opt({unix, darwin}) -> - %% netinet/in.h - %% #define IPPROTO_TCP 6 - %% - %% netinet/tcp.h - %% #define TCP_CONNECTION_INFO 0x106 - %% - {raw, 6, 16#106, 1}; -tcp_info_opt({unix, freebsd}) -> - %% sys/netinet/in.h - %% #define IPPROTO_TCP 6 - %% - %% sys/netinet/tcp.h - %% #define TCP_INFO 32 - %% - {raw, 6, 32, 1}; -tcp_info_opt({_, _}) -> - undefined. - -tcp_is_closed(State, {unix, linux}) -> - %% netinet/tcp.h - %% enum - %% { - %% TCP_ESTABLISHED = 1, - %% TCP_SYN_SENT, - %% TCP_SYN_RECV, - %% TCP_FIN_WAIT1, - %% TCP_FIN_WAIT2, - %% TCP_TIME_WAIT, - %% TCP_CLOSE, - %% TCP_CLOSE_WAIT, - %% TCP_LAST_ACK, - %% TCP_LISTEN, - %% TCP_CLOSING - %% } - %% - lists:member(State, [4, 5, 6, 7, 8, 9, 11]); -tcp_is_closed(State, {unix, Type}) when Type =:= darwin; Type =:= freebsd -> - %% tcp_fsm.h states are the same on macos and freebsd - %% - %% netinet/tcp_fsm.h - %% #define TCPS_CLOSED 0 /* closed */ - %% #define TCPS_LISTEN 1 /* listening for connection */ - %% #define TCPS_SYN_SENT 2 /* active, have sent syn */ - %% #define TCPS_SYN_RECEIVED 3 /* have send and received syn */ - %% #define TCPS_ESTABLISHED 4 /* established */ - %% #define TCPS_CLOSE_WAIT 5 /* rcvd fin, waiting for close */ - %% #define TCPS_FIN_WAIT_1 6 /* have closed, sent fin */ - %% #define TCPS_CLOSING 7 /* closed xchd FIN; await FIN ACK */ - %% #define TCPS_LAST_ACK 8 /* had fin and close; await FIN ACK */ - %% #define TCPS_FIN_WAIT_2 9 /* have closed, fin is acked */ - %% #define TCPS_TIME_WAIT 10 /* in 2*msl quiet wait after close */ - %% - lists:member(State, [0, 5, 6, 7, 8, 9, 10]). diff --git a/src/chttpd/test/eunit/chttpd_util_test.erl b/src/chttpd/test/eunit/chttpd_util_test.erl index 76edffeac..fbfd532cc 100644 --- a/src/chttpd/test/eunit/chttpd_util_test.erl +++ b/src/chttpd/test/eunit/chttpd_util_test.erl @@ -120,27 +120,29 @@ chttpd_util_client_socker_monitor_test_() -> fun test_util:start_couch/0, fun test_util:stop_couch/1, with([ - ?TDEF(t_socket_set_get_clean), - ?TDEF(t_socket_check_config), + ?TDEF(t_client_req_set_get_clean), + ?TDEF(t_client_req_check_config), ?TDEF(t_closed_socket_kills_coordinator) ]) }. -t_socket_set_get_clean(_) -> - ?assertEqual(undefined, chttpd_util:mochiweb_socket_get()), +t_client_req_set_get_clean(_) -> + ?assertEqual(undefined, chttpd_util:mochiweb_client_req_get()), {ok, Sock} = gen_tcp:listen(0, [{active, false}]), - chttpd_util:mochiweb_socket_set(Sock), - ?assertEqual(Sock, chttpd_util:mochiweb_socket_get()), - chttpd_util:mochiweb_socket_clean(), - ?assertEqual(undefined, chttpd_util:mochiweb_socket_get()), + Headers = mochiweb_headers:make([]), + ClientReq = mochiweb_request:new(Sock, 'GET', "/foo", {1, 1}, Headers), + chttpd_util:mochiweb_client_req_set(ClientReq), + ?assertEqual(ClientReq, chttpd_util:mochiweb_client_req_get()), + chttpd_util:mochiweb_client_req_clean(), + ?assertEqual(undefined, chttpd_util:mochiweb_client_req_get()), gen_tcp:close(Sock). -t_socket_check_config(_) -> +t_client_req_check_config(_) -> config:set("chttpd", "disconnect_check_msec", "100", false), config:set("chttpd", "disconnect_check_jitter_msec", "50", false), lists:foreach( fun(_) -> - MSec = chttpd_util:mochiweb_socket_check_msec(), + MSec = chttpd_util:mochiweb_client_req_check_msec(), ?assert(is_integer(MSec)), ?assert(MSec >= 100), ?assert(MSec =< 150) @@ -153,11 +155,12 @@ t_socket_check_config(_) -> t_closed_socket_kills_coordinator(_) -> {Pid, Ref} = spawn_coord(), {ok, Sock} = gen_tcp:listen(0, [{active, false}]), - + Headers = mochiweb_headers:make([]), + ClientReq = mochiweb_request:new(Sock, 'GET', "/foo", {1, 1}, Headers), % Can call getopts many times in a row process should stay alive lists:foreach( fun(_) -> - ok = chttpd_util:stop_client_process_if_disconnected(Pid, Sock) + ok = chttpd_util:stop_client_process_if_disconnected(Pid, ClientReq) end, lists:seq(1, 10000) ), @@ -165,7 +168,7 @@ t_closed_socket_kills_coordinator(_) -> gen_tcp:close(Sock), - ?assertEqual(ok, chttpd_util:stop_client_process_if_disconnected(Pid, Sock)), + ?assertEqual(ok, chttpd_util:stop_client_process_if_disconnected(Pid, ClientReq)), case tcp_info_works() of true -> ?assertEqual({shutdown, client_disconnected}, wait_coord_death(Ref)); @@ -178,7 +181,7 @@ t_closed_socket_kills_coordinator(_) -> % Can call stop_client_... even if process may be dead and the socket is closed lists:foreach( fun(_) -> - ok = chttpd_util:stop_client_process_if_disconnected(Pid, Sock) + ok = chttpd_util:stop_client_process_if_disconnected(Pid, ClientReq) end, lists:seq(1, 10000) ). @@ -198,7 +201,7 @@ wait_coord_death(Ref) -> tcp_info_works() -> case os:type() of {unix, OsName} -> - lists:member(OsName, [linux, freebsd, darwin]); + lists:member(OsName, [linux, freebsd, netbsd, openbsd, darwin]); {_, _} -> false end. diff --git a/src/fabric/src/fabric_db_update_listener.erl b/src/fabric/src/fabric_db_update_listener.erl index d4a49f37d..4f3c30a25 100644 --- a/src/fabric/src/fabric_db_update_listener.erl +++ b/src/fabric/src/fabric_db_update_listener.erl @@ -36,12 +36,12 @@ shards }). -go(Parent, ParentRef, DbName, Timeout, ClientSock) -> +go(Parent, ParentRef, DbName, Timeout, ClientReq) -> Shards = mem3:shards(DbName), Notifiers = start_update_notifiers(Shards), MonRefs = lists:usort([rexi_utils:server_pid(N) || #worker{node = N} <- Notifiers]), RexiMon = rexi_monitor:start(MonRefs), - MonPid = start_cleanup_monitor(self(), Notifiers, ClientSock), + MonPid = start_cleanup_monitor(self(), Notifiers, ClientReq), %% This is not a common pattern for rexi but to enable the calling %% process to communicate via handle_message/3 we "fake" it as a %% a spawned worker. @@ -97,17 +97,17 @@ handle_db_event(_DbName, deleted, St) -> handle_db_event(_DbName, _Event, St) -> {ok, St}. -start_cleanup_monitor(Parent, Notifiers, ClientSock) -> +start_cleanup_monitor(Parent, Notifiers, ClientReq) -> spawn(fun() -> Ref = erlang:monitor(process, Parent), - cleanup_monitor(Parent, Ref, Notifiers, ClientSock) + cleanup_monitor(Parent, Ref, Notifiers, ClientReq) end). stop_cleanup_monitor(MonPid) -> MonPid ! {self(), stop}. -cleanup_monitor(Parent, Ref, Notifiers, ClientSock) -> - CheckMSec = chttpd_util:mochiweb_socket_check_msec(), +cleanup_monitor(Parent, Ref, Notifiers, ClientReq) -> + CheckMSec = chttpd_util:mochiweb_client_req_check_msec(), receive {'DOWN', Ref, _, _, _} -> stop_update_notifiers(Notifiers); @@ -118,8 +118,8 @@ cleanup_monitor(Parent, Ref, Notifiers, ClientSock) -> stop_update_notifiers(Notifiers), exit(Parent, {unknown_message, Else}) after CheckMSec -> - chttpd_util:stop_client_process_if_disconnected(Parent, ClientSock), - cleanup_monitor(Parent, Ref, Notifiers, ClientSock) + chttpd_util:stop_client_process_if_disconnected(Parent, ClientReq), + cleanup_monitor(Parent, Ref, Notifiers, ClientReq) end. stop_update_notifiers(Notifiers) -> diff --git a/src/fabric/src/fabric_streams.erl b/src/fabric/src/fabric_streams.erl index 905e836a1..8c9a6e9a4 100644 --- a/src/fabric/src/fabric_streams.erl +++ b/src/fabric/src/fabric_streams.erl @@ -43,8 +43,8 @@ start(Workers0, Keypos, StartFun, Replacements, RingOpts) -> replacements = Replacements, ring_opts = RingOpts }, - ClientSock = chttpd_util:mochiweb_socket_get(), - spawn_worker_cleaner(self(), Workers0, ClientSock), + ClientReq = chttpd_util:mochiweb_client_req_get(), + spawn_worker_cleaner(self(), Workers0, ClientReq), Timeout = fabric_util:request_timeout(), case rexi_utils:recv(Workers0, Keypos, Fun, Acc, Timeout, infinity) of {ok, #stream_acc{ready = Workers}} -> @@ -156,12 +156,12 @@ handle_stream_start(Else, _, _) -> % Spawn an auxiliary rexi worker cleaner. This will be used in cases % when the coordinator (request) process is forceably killed and doesn't % get a chance to process its `after` fabric:clean/1 clause. -spawn_worker_cleaner(Coordinator, Workers, ClientSock) -> +spawn_worker_cleaner(Coordinator, Workers, ClientReq) -> case get(?WORKER_CLEANER) of undefined -> Pid = spawn(fun() -> erlang:monitor(process, Coordinator), - cleaner_loop(Coordinator, Workers, ClientSock) + cleaner_loop(Coordinator, Workers, ClientReq) end), put(?WORKER_CLEANER, Pid), Pid; @@ -169,16 +169,16 @@ spawn_worker_cleaner(Coordinator, Workers, ClientSock) -> ExistingCleaner end. -cleaner_loop(Pid, Workers, ClientSock) -> - CheckMSec = chttpd_util:mochiweb_socket_check_msec(), +cleaner_loop(Pid, Workers, ClientReq) -> + CheckMSec = chttpd_util:mochiweb_client_req_check_msec(), receive {add_worker, Pid, Worker} -> - cleaner_loop(Pid, [Worker | Workers], ClientSock); + cleaner_loop(Pid, [Worker | Workers], ClientReq); {'DOWN', _, _, Pid, _} -> fabric_util:cleanup(Workers) after CheckMSec -> - chttpd_util:stop_client_process_if_disconnected(Pid, ClientSock), - cleaner_loop(Pid, Workers, ClientSock) + chttpd_util:stop_client_process_if_disconnected(Pid, ClientReq), + cleaner_loop(Pid, Workers, ClientReq) end. add_worker_to_cleaner(CoordinatorPid, Worker) -> @@ -285,14 +285,22 @@ coordinator_is_killed_if_client_disconnects(_) -> die -> ok end end), + Headers = mochiweb_headers:make([]), {ok, Sock} = gen_tcp:listen(0, [{active, false}]), + ClientReq = mochiweb_request:new(Sock, 'GET', "/foo", {1, 1}, Headers), % Close the socket and then expect coordinator to be killed ok = gen_tcp:close(Sock), - Cleaner = spawn_worker_cleaner(Coord, Workers, Sock), + Cleaner = spawn_worker_cleaner(Coord, Workers, ClientReq), CleanerRef = erlang:monitor(process, Cleaner), % Assert the correct behavior on the support platforms (all except Windows so far) case os:type() of - {unix, Type} when Type =:= linux; Type =:= darwin; Type =:= freebsd -> + {unix, Type} when + Type =:= linux; + Type =:= darwin; + Type =:= freebsd; + Type =:= openbsd; + Type =:= netbsd + -> % Coordinator should be torn down receive {'DOWN', CoordRef, _, _, Reason} -> @@ -320,8 +328,10 @@ coordinator_is_not_killed_if_client_is_connected(_) -> die -> ok end end), + Headers = mochiweb_headers:make([]), {ok, Sock} = gen_tcp:listen(0, [{active, false}]), - Cleaner = spawn_worker_cleaner(Coord, Workers, Sock), + ClientReq = mochiweb_request:new(Sock, 'GET', "/foo", {1, 1}, Headers), + Cleaner = spawn_worker_cleaner(Coord, Workers, ClientReq), CleanerRef = erlang:monitor(process, Cleaner), % Coordinator should stay up receive @@ -342,7 +352,7 @@ coordinator_is_not_killed_if_client_is_connected(_) -> setup() -> ok = meck:expect(rexi, kill_all, fun(_) -> ok end), % Speed up disconnect socket timeout for the test to 200 msec - ok = meck:expect(chttpd_util, mochiweb_socket_check_msec, 0, 200). + ok = meck:expect(chttpd_util, mochiweb_client_req_check_msec, 0, 200). teardown(_) -> meck:unload(). diff --git a/src/fabric/src/fabric_view_changes.erl b/src/fabric/src/fabric_view_changes.erl index 2644cd59d..32e16fbc5 100644 --- a/src/fabric/src/fabric_view_changes.erl +++ b/src/fabric/src/fabric_view_changes.erl @@ -39,12 +39,12 @@ go(DbName, Feed, Options, Callback, Acc0) when {Timeout, _} = couch_changes:get_changes_timeout(Args, Callback), Ref = make_ref(), Parent = self(), - ClientSock = chttpd_util:mochiweb_socket_get(), + ClientReq = chttpd_util:mochiweb_client_req_get(), UpdateListener = { spawn_link( fabric_db_update_listener, go, - [Parent, Ref, DbName, Timeout, ClientSock] + [Parent, Ref, DbName, Timeout, ClientReq] ), Ref },