From 1de8154586c1d78184625ef86cdcbe8f6f9d4652 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Thu, 24 Jul 2014 23:30:07 +0100 Subject: [PATCH 01/45] New pipelining algorithm --- include/ibrowse.hrl | 2 +- src/ibrowse.erl | 76 ++++++++++++------------------------ src/ibrowse_http_client.erl | 11 ++---- src/ibrowse_lb.erl | 55 +++++++++++++------------- test/ibrowse_load_test.erl | 77 +++++++++++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 90 deletions(-) create mode 100644 test/ibrowse_load_test.erl diff --git a/include/ibrowse.hrl b/include/ibrowse.hrl index 18dde82..150b1b7 100644 --- a/include/ibrowse.hrl +++ b/include/ibrowse.hrl @@ -12,7 +12,7 @@ host_type % 'hostname', 'ipv4_address' or 'ipv6_address' }). --record(lb_pid, {host_port, pid}). +-record(lb_pid, {host_port, pid, ets_tid}). -record(client_conn, {key, cur_pipeline_size = 0, reqs_served = 0}). diff --git a/src/ibrowse.erl b/src/ibrowse.erl index 85bb75c..3d62224 100644 --- a/src/ibrowse.erl +++ b/src/ibrowse.erl @@ -638,8 +638,8 @@ show_dest_status(Url) -> show_dest_status(Host, Port) -> case get_metrics(Host, Port) of {Lb_pid, MsgQueueSize, Tid, Size, - {{First_p_sz, First_speculative_sz}, - {Last_p_sz, Last_speculative_sz}}} -> + {{First_p_sz, _}, + {Last_p_sz, _}}} -> io:format("Load Balancer Pid : ~p~n" "LB process msg q size : ~p~n" "LB ETS table id : ~p~n" @@ -647,66 +647,39 @@ show_dest_status(Host, Port) -> "Smallest pipeline : ~p:~p~n" "Largest pipeline : ~p:~p~n", [Lb_pid, MsgQueueSize, Tid, Size, - First_p_sz, First_speculative_sz, - Last_p_sz, Last_speculative_sz]); + First_p_sz, First_p_sz, + Last_p_sz, Last_p_sz + ]); _Err -> io:format("Metrics not available~n", []) end. get_metrics() -> - Dests = lists:filter(fun({lb_pid, {Host, Port}, _}) when is_list(Host), - is_integer(Port) -> - true; - (_) -> - false - end, ets:tab2list(ibrowse_lb)), - All_ets = ets:all(), - lists:map(fun({lb_pid, {Host, Port}, Lb_pid}) -> - case lists:dropwhile( - fun(Tid) -> - ets:info(Tid, owner) /= Lb_pid - end, All_ets) of - [] -> - {Host, Port, Lb_pid, unknown, 0}; - [Tid | _] -> - Size = case catch (ets:info(Tid, size)) of - N when is_integer(N) -> N; - _ -> 0 - end, - {Host, Port, Lb_pid, Tid, Size} - end - end, Dests). + [get_metrics(Host, Port) || #lb_pid{host_port = {Host, Port}} <- + ets:tab2list(ibrowse_lb), + is_list(Host), + is_integer(Port)]. get_metrics(Host, Port) -> case ets:lookup(ibrowse_lb, {Host, Port}) of [] -> no_active_processes; - [#lb_pid{pid = Lb_pid}] -> + [#lb_pid{pid = Lb_pid, ets_tid = Tid}] -> MsgQueueSize = (catch process_info(Lb_pid, message_queue_len)), - %% {Lb_pid, MsgQueueSize, - case lists:dropwhile( - fun(Tid) -> - ets:info(Tid, owner) /= Lb_pid - end, ets:all()) of - [] -> - {Lb_pid, MsgQueueSize, unknown, 0, unknown}; - [Tid | _] -> - try - Size = ets:info(Tid, size), - case Size of - 0 -> - ok; - _ -> - First = ets:first(Tid), - Last = ets:last(Tid), - [{_, First_p_sz, First_speculative_sz}] = ets:lookup(Tid, First), - [{_, Last_p_sz, Last_speculative_sz}] = ets:lookup(Tid, Last), - {Lb_pid, MsgQueueSize, Tid, Size, - {{First_p_sz, First_speculative_sz}, {Last_p_sz, Last_speculative_sz}}} - end - catch _:_ -> - not_available - end + try + Size = ets:info(Tid, size), + case Size of + 0 -> + ok; + _ -> + {First_p_sz, _} = ets:first(Tid), + {Last_p_sz, _} = ets:last(Tid), + {Lb_pid, MsgQueueSize, Tid, Size, + {{First_p_sz, First_p_sz}, + {Last_p_sz, Last_p_sz}}} + end + catch _:_ -> + not_available end end. @@ -944,7 +917,6 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- do_get_connection(#url{host = Host, port = Port}, []) -> {ok, Pid} = ibrowse_lb:start_link([Host, Port]), - ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, pid = Pid}), Pid; do_get_connection(_Url, [#lb_pid{pid = Pid}]) -> Pid. diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index eef8b9f..04797e9 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -1941,14 +1941,9 @@ inc_pipeline_counter(#state{is_closing = true} = State) -> State; inc_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> State; -inc_pipeline_counter(#state{cur_pipeline_size = Pipe_sz, - lb_ets_tid = Tid} = State) -> - update_counter(Tid, self(), {2,1,99999,9999}), +inc_pipeline_counter(#state{cur_pipeline_size = Pipe_sz} = State) -> State#state{cur_pipeline_size = Pipe_sz + 1}. -update_counter(Tid, Key, Args) -> - ets:update_counter(Tid, Key, Args). - dec_pipeline_counter(#state{is_closing = true} = State) -> State; dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> @@ -1956,8 +1951,8 @@ dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz, lb_ets_tid = Tid} = State) -> _ = try - update_counter(Tid, self(), {2,-1,0,0}), - update_counter(Tid, self(), {3,-1,0,0}) + ets:insert(Tid, {{Pipe_sz - 1, self()}, []}), + ets:delete(Tid, {Pipe_sz, self()}) catch _:_ -> ok diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index f5a9aef..cbb2539 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -17,7 +17,8 @@ -export([ start_link/1, spawn_connection/6, - stop/1 + stop/1, + proc_name/2 ]). %% gen_server callbacks @@ -49,8 +50,12 @@ %% Function: start_link/0 %% Description: Starts the server %%-------------------------------------------------------------------- -start_link(Args) -> - gen_server:start_link(?MODULE, Args, []). +start_link([Host, Port] = Args) -> + Name = proc_name(Host, Port), + gen_server:start_link({local, Name}, ?MODULE, Args, []). + +proc_name(Host, Port) -> + list_to_atom("ibrowse_lb_" ++ Host ++ "_" ++ integer_to_list(Port)). %%==================================================================== %% Server functions @@ -70,13 +75,13 @@ init([Host, Port]) -> Max_pipe_sz = ibrowse:get_config_value({max_pipeline_size, Host, Port}, 10), put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)), put(ibrowse_trace_token, ["LB: ", Host, $:, integer_to_list(Port)]), - Tid = ets:new(ibrowse_lb, [public, ordered_set]), - {ok, #state{parent_pid = whereis(ibrowse), + State = #state{parent_pid = whereis(ibrowse), host = Host, port = Port, - ets_tid = Tid, max_pipeline_size = Max_pipe_sz, - max_sessions = Max_sessions}}. + max_sessions = Max_sessions}, + State_1 = maybe_create_ets(State), + {ok, State_1}. spawn_connection(Lb_pid, Url, Max_sessions, @@ -137,7 +142,7 @@ handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_opt State_1 = maybe_create_ets(State), Tid = State_1#state.ets_tid, {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options), - ets:insert(Tid, {Pid, 0, 0}), + ets:insert(Tid, {{0, Pid}, []}), {reply, {ok, Pid}, State_1#state{num_cur_sessions = Cur + 1, max_sessions = Max_sess, max_pipeline_size = Max_pipe}}; @@ -231,30 +236,22 @@ code_change(_OldVsn, State, _Extra) -> %%% Internal functions %%-------------------------------------------------------------------- find_best_connection(Tid, Max_pipe) -> - ets:safe_fixtable(Tid, true), - Res = find_best_connection(ets:first(Tid), Tid, Max_pipe), - ets:safe_fixtable(Tid, false), - Res. - -find_best_connection('$end_of_table', _, _) -> - {error, retry_later}; -find_best_connection(Pid, Tid, Max_pipe) -> - case ets:lookup(Tid, Pid) of - [{Pid, Cur_sz, Speculative_sz}] when Cur_sz < Max_pipe, - Speculative_sz < Max_pipe -> - case catch ets:update_counter(Tid, Pid, {3, 1, 9999999, 9999999}) of - {'EXIT', _} -> - %% The selected process has shutdown - find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe); - _ -> - {ok, Pid} - end; - _ -> - find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe) + First = ets:first(Tid), + case First of + {Pid_pipeline_size, Pid} when Pid_pipeline_size < Max_pipe -> + ets:delete(Tid, First), + ets:insert(Tid, {{Pid_pipeline_size, Pid}, []}), + {ok, Pid}; + _ -> + {error, retry_later} end. -maybe_create_ets(#state{ets_tid = undefined} = State) -> +maybe_create_ets(#state{ets_tid = undefined, + host = Host, port = Port} = State) -> Tid = ets:new(ibrowse_lb, [public, ordered_set]), + ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, + pid = self(), + ets_tid = Tid}), State#state{ets_tid = Tid}; maybe_create_ets(State) -> State. diff --git a/test/ibrowse_load_test.erl b/test/ibrowse_load_test.erl new file mode 100644 index 0000000..3219314 --- /dev/null +++ b/test/ibrowse_load_test.erl @@ -0,0 +1,77 @@ +-module(ibrowse_load_test). +-export([go/3]). + +-define(counters, ibrowse_load_test_counters). + +go(URL, N_workers, N_reqs) -> + spawn(fun() -> + go_1(URL, N_workers, N_reqs) + end). + +go_1(URL, N_workers, N_reqs) -> + ets:new(?counters, [named_table, public]), + try + ets:insert(?counters, [{success, 0}, + {failed, 0}, + {timeout, 0}, + {retry_later, 0}]), + Start_time = now(), + Pids = spawn_workers(N_workers, N_reqs, URL, self(), []), + wait_for_pids(Pids), + End_time = now(), + Time_taken = trunc(round(timer:now_diff(End_time, Start_time) / 1000000)), + [{_, Success_reqs}] = ets:lookup(?counters, success), + Total_reqs = N_workers*N_reqs, + Req_rate = case Time_taken > 0 of + true -> + trunc(Success_reqs / Time_taken); + false when Success_reqs == Total_reqs -> + withabix; + false -> + without_a_bix + end, + io:format("Stats : ~p~n", [ets:tab2list(?counters)]), + io:format("Total reqs : ~p~n", [Total_reqs]), + io:format("Time taken : ~p seconds~n", [Time_taken]), + io:format("Reqs / sec : ~p~n", [Req_rate]) + catch Class:Reason -> + io:format("Load test crashed. Reason: ~p~n" + "Stacktrace : ~p~n", + [{Class, Reason}, erlang:get_stacktrace()]) + after + ets:delete(?counters) + end. + +spawn_workers(0, _, _, _, Acc) -> + Acc; +spawn_workers(N_workers, N_reqs, URL, Parent, Acc) -> + Pid = spawn(fun() -> + worker(N_reqs, URL, Parent) + end), + spawn_workers(N_workers - 1, N_reqs, URL, Parent, [Pid | Acc]). + +wait_for_pids([Pid | T]) -> + receive + {done, Pid} -> + wait_for_pids(T); + {done, Some_pid} -> + wait_for_pids([Pid | (T -- [Some_pid])]) + end; +wait_for_pids([]) -> + ok. + + +worker(0, _, Parent) -> + Parent ! {done, self()}; +worker(N, URL, Parent) -> + case ibrowse:send_req(URL, [], get) of + {ok, "200", _, _} -> + ets:update_counter(?counters, success, 1); + {error, req_timedout} -> + ets:update_counter(?counters, timeout, 1); + {error, retry_later} -> + ets:update_counter(?counters, retry_later, 1); + _ -> + ets:update_counter(?counters, failed, 1) + end, + worker(N - 1, URL, Parent). From 502f73b9e66482a442efa56331c8702f07202cc5 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Fri, 25 Jul 2014 12:39:29 +0100 Subject: [PATCH 02/45] Don't need named ibrowse_lb process --- src/ibrowse_lb.erl | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index cbb2539..2b34ddb 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -17,8 +17,7 @@ -export([ start_link/1, spawn_connection/6, - stop/1, - proc_name/2 + stop/1 ]). %% gen_server callbacks @@ -50,12 +49,8 @@ %% Function: start_link/0 %% Description: Starts the server %%-------------------------------------------------------------------- -start_link([Host, Port] = Args) -> - Name = proc_name(Host, Port), - gen_server:start_link({local, Name}, ?MODULE, Args, []). - -proc_name(Host, Port) -> - list_to_atom("ibrowse_lb_" ++ Host ++ "_" ++ integer_to_list(Port)). +start_link(Args) -> + gen_server:start_link(?MODULE, Args, []). %%==================================================================== %% Server functions From ba6652f50b4aba93237089ced80b5da874f3dc8a Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Fri, 8 Aug 2014 15:45:51 +0100 Subject: [PATCH 03/45] More fixes to pipelining --- src/ibrowse.erl | 132 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 90 insertions(+), 42 deletions(-) diff --git a/src/ibrowse.erl b/src/ibrowse.erl index 3d62224..6c87364 100644 --- a/src/ibrowse.erl +++ b/src/ibrowse.erl @@ -158,7 +158,7 @@ stop() -> %% respHeader() = {headerName(), headerValue()} %% headerName() = string() %% headerValue() = string() -%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason} +%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason} %% req_id() = term() %% ResponseBody = string() | {file, Filename} %% Reason = term() @@ -252,6 +252,11 @@ send_req(Url, Headers, Method, Body) -> %% headers. Not quite sure why someone would want this, but one of my %% users asked for it, so here it is. %% +%%
  • The preserve_status_line option is to get the raw status line as a custom header +%% in the response. The status line is returned as a tuple {ibrowse_status_line, Status_line_binary} +%% If both the give_raw_headers and preserve_status_line are specified +%% in a request, only the give_raw_headers is honoured.
  • +%% %%
  • The preserve_chunked_encoding option enables the caller %% to receive the raw data stream when the Transfer-Encoding of the server %% response is Chunked. @@ -336,7 +341,7 @@ send_req(Url, Headers, Method, Body, Options, Timeout) -> Max_sessions, Max_pipeline_size, {SSLOptions, IsSSL}, - Headers, Method, Body, Options_1, Timeout, 0); + Headers, Method, Body, Options_1, Timeout, Timeout, os:timestamp(), 0); Err -> {error, {url_parsing_failed, Err}} end. @@ -345,29 +350,41 @@ try_routing_request(Lb_pid, Parsed_url, Max_sessions, Max_pipeline_size, {SSLOptions, IsSSL}, - Headers, Method, Body, Options_1, Timeout, Try_count) when Try_count < 3 -> + Headers, Method, Body, Options_1, Timeout, + Ori_timeout, Req_start_time, Try_count) when Try_count =< 3 -> ProcessOptions = get_value(worker_process_options, Options_1, []), case ibrowse_lb:spawn_connection(Lb_pid, Parsed_url, Max_sessions, Max_pipeline_size, {SSLOptions, IsSSL}, ProcessOptions) of - {ok, Conn_Pid} -> + {ok, {_Pid_cur_spec_size, _, Conn_Pid}} -> case do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options_1, Timeout) of {error, sel_conn_closed} -> - try_routing_request(Lb_pid, Parsed_url, - Max_sessions, - Max_pipeline_size, - {SSLOptions, IsSSL}, - Headers, Method, Body, Options_1, Timeout, Try_count + 1); + Time_now = os:timestamp(), + Time_taken_so_far = trunc(round(timer:now_diff(Time_now, Req_start_time)/1000)), + Time_remaining = Ori_timeout - Time_taken_so_far, + Time_remaining_percent = trunc(round((Time_remaining/Ori_timeout)*100)), + %% io:format("~p -- Time_remaining: ~p (~p%)~n", [self(), Time_remaining, Time_remaining_percent]), + case (Time_remaining > 0) andalso (Time_remaining_percent >= 5) of + true -> + try_routing_request(Lb_pid, Parsed_url, + Max_sessions, + Max_pipeline_size, + {SSLOptions, IsSSL}, + Headers, Method, Body, Options_1, + Time_remaining, Ori_timeout, Req_start_time, Try_count + 1); + false -> + {error, retry_later} + end; Res -> Res end; Err -> Err end; -try_routing_request(_, _, _, _, _, _, _, _, _, _, _) -> +try_routing_request(_, _, _, _, _, _, _, _, _, _, _, _, _) -> {error, retry_later}. merge_options(Host, Port, Options) -> @@ -441,14 +458,29 @@ do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout) -> Headers, Method, ensure_bin(Body), Options, Timeout) of {'EXIT', {timeout, _}} -> + P_info = case catch erlang:process_info(Conn_Pid, [messages, message_queue_len, backtrace]) of + [_|_] = Conn_Pid_info_list -> + Conn_Pid_info_list; + _ -> + process_info_not_available + end, + (catch lager:error("{ibrowse_http_client, send_req, ~1000.p} gen_server call timeout.~nProcess info: ~p~n", + [[Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout], P_info])), {error, req_timedout}; - {'EXIT', {noproc, {gen_server, call, [Conn_Pid, _, _]}}} -> - {error, sel_conn_closed}; - {'EXIT', {normal, _}} -> - {error, sel_conn_closed}; - {'EXIT', {connection_closed, _}} -> + {'EXIT', {normal, _}} = Ex_rsn -> + (catch lager:error("{ibrowse_http_client, send_req, ~1000.p} gen_server call got ~1000.p~n", + [[Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout], Ex_rsn])), + {error, req_timedout}; + {error, X} when X == connection_closed; + X == {send_failed, {error, enotconn}}; + X == {send_failed,{error,einval}}; + X == {send_failed,{error,closed}}; + X == connection_closing; + ((X == connection_closed_no_retry) andalso ((Method == get) orelse (Method == head))) -> {error, sel_conn_closed}; - {error, connection_closed} -> + {error, connection_closed_no_retry} -> + {error, connection_closed}; + {error, {'EXIT', {noproc, _}}} -> {error, sel_conn_closed}; {'EXIT', Reason} -> {error, {'EXIT', Reason}}; @@ -637,28 +669,39 @@ show_dest_status(Url) -> %% included. show_dest_status(Host, Port) -> case get_metrics(Host, Port) of - {Lb_pid, MsgQueueSize, Tid, Size, - {{First_p_sz, _}, - {Last_p_sz, _}}} -> + {Lb_pid, MsgQueueSize, + Tid, Size, + {{First_p_sz, First_p_sz}, + {Last_p_sz, Last_p_sz}}} -> io:format("Load Balancer Pid : ~p~n" "LB process msg q size : ~p~n" "LB ETS table id : ~p~n" "Num Connections : ~p~n" - "Smallest pipeline : ~p:~p~n" - "Largest pipeline : ~p:~p~n", + "Smallest pipeline : ~p~n" + "Largest pipeline : ~p~n", [Lb_pid, MsgQueueSize, Tid, Size, - First_p_sz, First_p_sz, - Last_p_sz, Last_p_sz - ]); + First_p_sz, Last_p_sz]); _Err -> io:format("Metrics not available~n", []) end. get_metrics() -> - [get_metrics(Host, Port) || #lb_pid{host_port = {Host, Port}} <- - ets:tab2list(ibrowse_lb), - is_list(Host), - is_integer(Port)]. + Dests = lists:filter( + fun(#lb_pid{host_port = {Host, Port}}) when is_list(Host), + is_integer(Port) -> + true; + (_) -> + false + end, ets:tab2list(ibrowse_lb)), + lists:foldl( + fun(#lb_pid{host_port = {X_host, X_port}}, X_acc) -> + case get_metrics(X_host, X_port) of + {_, _, _, _, _} = X_res -> + [X_res | X_acc]; + _X_res -> + X_acc + end + end, [], Dests). get_metrics(Host, Port) -> case ets:lookup(ibrowse_lb, {Host, Port}) of @@ -666,20 +709,25 @@ get_metrics(Host, Port) -> no_active_processes; [#lb_pid{pid = Lb_pid, ets_tid = Tid}] -> MsgQueueSize = (catch process_info(Lb_pid, message_queue_len)), - try - Size = ets:info(Tid, size), - case Size of - 0 -> - ok; - _ -> - {First_p_sz, _} = ets:first(Tid), - {Last_p_sz, _} = ets:last(Tid), - {Lb_pid, MsgQueueSize, Tid, Size, - {{First_p_sz, First_p_sz}, - {Last_p_sz, Last_p_sz}}} - end - catch _:_ -> - not_available + case Tid of + undefined -> + {Lb_pid, MsgQueueSize, undefined, 0, {{0, 0}, {0, 0}}}; + _ -> + try + Size = ets:info(Tid, size), + case Size of + 0 -> + {Lb_pid, MsgQueueSize, undefined, 0, {{0, 0}, {0, 0}}}; + _ -> + {First_p_sz, _, _} = ets:first(Tid), + {Last_p_sz, _, _} = ets:last(Tid), + {Lb_pid, MsgQueueSize, + Tid, Size, + {{First_p_sz, First_p_sz}, {Last_p_sz, Last_p_sz}}} + end + catch _:_Err -> + not_available + end end end. From b8b6add47f0b5e32d8bff0daa1114989be3877c0 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Fri, 8 Aug 2014 15:46:22 +0100 Subject: [PATCH 04/45] More fixes to pipelining --- src/ibrowse_lib.erl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ibrowse_lib.erl b/src/ibrowse_lib.erl index 1ce6bd4..1098b0f 100644 --- a/src/ibrowse_lib.erl +++ b/src/ibrowse_lib.erl @@ -28,7 +28,8 @@ get_value/2, get_value/3, parse_url/1, - printable_date/0 + printable_date/0, + printable_date/1 ]). get_trace_status(Host, Port) -> @@ -367,8 +368,11 @@ default_port(https) -> 443; default_port(ftp) -> 21. printable_date() -> - {{Y,Mo,D},{H, M, S}} = calendar:local_time(), - {_,_,MicroSecs} = now(), + printable_date(os:timestamp()). + +printable_date(Now) -> + {{Y,Mo,D},{H, M, S}} = calendar:now_to_local_time(Now), + {_,_,MicroSecs} = Now, [integer_to_list(Y), $-, integer_to_list(Mo), From 47ae6a42179188cb79acf17ed71ce85343dcb629 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Fri, 8 Aug 2014 15:46:50 +0100 Subject: [PATCH 05/45] More fixes to pipelining --- src/ibrowse_lb.erl | 84 ++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 51 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 2b34ddb..a802f87 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -36,7 +36,6 @@ port, max_sessions, max_pipeline_size, - num_cur_sessions = 0, proc_state }). @@ -123,24 +122,23 @@ handle_call(stop, _From, #state{ets_tid = Tid} = State) -> handle_call(_, _From, #state{proc_state = shutting_down} = State) -> {reply, {error, shutting_down}, State}; -%% Update max_sessions in #state with supplied value -handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _, _}, _From, - #state{num_cur_sessions = Num} = State) - when Num >= Max_sess -> - State_1 = maybe_create_ets(State), - Reply = find_best_connection(State_1#state.ets_tid, Max_pipe), - {reply, Reply, State_1#state{max_sessions = Max_sess, - max_pipeline_size = Max_pipe}}; - handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From, - #state{num_cur_sessions = Cur} = State) -> - State_1 = maybe_create_ets(State), - Tid = State_1#state.ets_tid, - {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options), - ets:insert(Tid, {{0, Pid}, []}), - {reply, {ok, Pid}, State_1#state{num_cur_sessions = Cur + 1, - max_sessions = Max_sess, - max_pipeline_size = Max_pipe}}; + State) -> + State_1 = maybe_create_ets(State), + Tid = State_1#state.ets_tid, + Tid_size = ets:info(Tid, size), + case Tid_size > Max_sess of + true -> + Reply = find_best_connection(Tid, Max_pipe, Tid_size), + {reply, Reply, State_1#state{max_sessions = Max_sess, + max_pipeline_size = Max_pipe}}; + false -> + {ok, Pid} = ibrowse_http_client:start({Tid, Url, SSL_options}, Process_options), + Ts = os:timestamp(), + ets:insert(Tid, {{0, Ts, Pid}, []}), + {reply, {ok, {0, Ts, Pid}}, State_1#state{max_sessions = Max_sess, + max_pipeline_size = Max_pipe}} + end; handle_call(Request, _From, State) -> Reply = {unknown_request, Request}, @@ -163,24 +161,6 @@ handle_cast(_Msg, State) -> %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- -handle_info({'EXIT', Parent, _Reason}, #state{parent_pid = Parent} = State) -> - {stop, normal, State}; - -handle_info({'EXIT', _Pid, _Reason}, #state{ets_tid = undefined} = State) -> - {noreply, State}; - -handle_info({'EXIT', Pid, _Reason}, - #state{num_cur_sessions = Cur, - ets_tid = Tid} = State) -> - ets:match_delete(Tid, {{'_', Pid}, '_'}), - Cur_1 = Cur - 1, - case Cur_1 of - 0 -> - ets:delete(Tid), - {noreply, State#state{ets_tid = undefined, num_cur_sessions = 0}, 10000}; - _ -> - {noreply, State#state{num_cur_sessions = Cur_1}} - end; handle_info({trace, Bool}, #state{ets_tid = undefined} = State) -> put(my_trace_flag, Bool), @@ -216,7 +196,8 @@ handle_info(_Info, State) -> %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- -terminate(_Reason, _State) -> +terminate(_Reason, #state{host = Host, port = Port} = _State) -> + catch ets:delete(ibrowse_lb, {Host, Port}), ok. %%-------------------------------------------------------------------- @@ -230,23 +211,24 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -find_best_connection(Tid, Max_pipe) -> - First = ets:first(Tid), - case First of - {Pid_pipeline_size, Pid} when Pid_pipeline_size < Max_pipe -> - ets:delete(Tid, First), - ets:insert(Tid, {{Pid_pipeline_size, Pid}, []}), - {ok, Pid}; - _ -> - {error, retry_later} +find_best_connection(Tid, Max_pipe, _Num_cur) -> + case ets:first(Tid) of + {Spec_size, Ts, Pid} = First -> + case Spec_size >= Max_pipe of + true -> + {error, retry_later}; + false -> + ets:delete(Tid, First), + ets:insert(Tid, {{Spec_size + 1, Ts, Pid}, []}), + {ok, First} + end; + '$end_of_table' -> + {error, retry_later} end. -maybe_create_ets(#state{ets_tid = undefined, - host = Host, port = Port} = State) -> +maybe_create_ets(#state{ets_tid = undefined, host = Host, port = Port} = State) -> Tid = ets:new(ibrowse_lb, [public, ordered_set]), - ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, - pid = self(), - ets_tid = Tid}), + ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, pid = self(), ets_tid = Tid}), State#state{ets_tid = Tid}; maybe_create_ets(State) -> State. From fb56bd1232ffb908ff9d8d7afbf76bf1d52bc6a8 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Fri, 8 Aug 2014 15:47:35 +0100 Subject: [PATCH 06/45] More fixes to pipelining --- src/ibrowse_http_client.erl | 215 ++++++++++++++++++++++++------------ 1 file changed, 145 insertions(+), 70 deletions(-) diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index 04797e9..db9559a 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -53,7 +53,8 @@ deleted_crlf = false, transfer_encoding, chunk_size, chunk_size_buffer = <<>>, recvd_chunk_size, interim_reply_sent = false, - lb_ets_tid, cur_pipeline_size = 0, prev_req_id + lb_ets_tid, cur_pipeline_size = 0, prev_req_id, + proc_state }). -record(request, {url, method, options, from, @@ -73,6 +74,12 @@ -define(DEFAULT_STREAM_CHUNK_SIZE, 1024*1024). -define(dec2hex(X), erlang:integer_to_list(X, 16)). + +%% Macros to prevent spelling mistakes causing bugs +-define(dont_retry_pipelined_requests, dont_retry_pipelined_requests). +-define(can_retry_pipelined_requests, can_retry_pipelined_requests). +-define(dead_proc_walking, dead_proc_walking). + %%==================================================================== %% External functions %%==================================================================== @@ -102,9 +109,15 @@ stop(Conn_pid) -> end. send_req(Conn_Pid, Url, Headers, Method, Body, Options, Timeout) -> - gen_server:call( - Conn_Pid, - {send_req, {Url, Headers, Method, Body, Options, Timeout}}, Timeout). + case catch gen_server:call(Conn_Pid, + {send_req, {Url, Headers, Method, Body, Options, Timeout}}, Timeout) of + {'EXIT', {timeout, _}} -> + {error, req_timedout}; + {'EXIT', {noproc, _}} -> + {error, connection_closed}; + Res -> + Res + end. %%==================================================================== %% Server functions @@ -119,6 +132,7 @@ send_req(Conn_Pid, Url, Headers, Method, Body, Options, Timeout) -> %% {stop, Reason} %%-------------------------------------------------------------------- init({Lb_Tid, #url{host = Host, port = Port}, {SSLOptions, Is_ssl}}) -> + process_flag(trap_exit, true), State = #state{host = Host, port = Port, ssl_options = SSLOptions, @@ -128,6 +142,7 @@ init({Lb_Tid, #url{host = Host, port = Port}, {SSLOptions, Is_ssl}}) -> put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)), {ok, set_inac_timer(State)}; init(Url) when is_list(Url) -> + process_flag(trap_exit, true), case catch ibrowse_lib:parse_url(Url) of #url{protocol = Protocol} = Url_rec -> init({undefined, Url_rec, {[], Protocol == https}}); @@ -135,6 +150,7 @@ init(Url) when is_list(Url) -> {error, invalid_url} end; init({Host, Port}) -> + process_flag(trap_exit, true), State = #state{host = Host, port = Port}, put(ibrowse_trace_token, [Host, $:, integer_to_list(Port)]), @@ -156,6 +172,10 @@ init({Host, Port}) -> handle_call({send_req, _}, _From, #state{is_closing = true} = State) -> {reply, {error, connection_closing}, State}; +handle_call({send_req, _}, _From, #state{proc_state = ?dead_proc_walking} = State) -> + shutting_down(State), + {reply, {error, connection_closing}, State}; + handle_call({send_req, {Url, Headers, Method, Body, Options, Timeout}}, From, State) -> send_req_1(From, Url, Headers, Method, Body, Options, Timeout, State); @@ -207,30 +227,40 @@ handle_info({stream_next, _Req_id}, State) -> {noreply, State}; handle_info({stream_close, _Req_id}, State) -> - shutting_down(State), - do_close(State), - do_error_reply(State, closing_on_request), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + do_close(State_1), + do_error_reply(State_1, closing_on_request), + delayed_stop_timer(), + {noreply, State_1}; handle_info({tcp_closed, _Sock}, State) -> do_trace("TCP connection closed by peer!~n", []), - handle_sock_closed(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + handle_sock_closed(State_1, ?can_retry_pipelined_requests), + delayed_stop_timer(), + {noreply, State_1}; handle_info({ssl_closed, _Sock}, State) -> do_trace("SSL connection closed by peer!~n", []), - handle_sock_closed(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + handle_sock_closed(State_1, ?can_retry_pipelined_requests), + delayed_stop_timer(), + {noreply, State_1}; handle_info({tcp_error, _Sock, Reason}, State) -> do_trace("Error on connection to ~1000.p:~1000.p -> ~1000.p~n", [State#state.host, State#state.port, Reason]), - handle_sock_closed(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + handle_sock_closed(State_1, ?dont_retry_pipelined_requests), + delayed_stop_timer(), + {noreply, State_1}; handle_info({ssl_error, _Sock, Reason}, State) -> do_trace("Error on SSL connection to ~1000.p:~1000.p -> ~1000.p~n", [State#state.host, State#state.port, Reason]), - handle_sock_closed(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + handle_sock_closed(State_1, ?dont_retry_pipelined_requests), + delayed_stop_timer(), + {noreply, State_1}; handle_info({req_timedout, From}, State) -> case lists:keysearch(From, #request.from, queue:to_list(State#state.reqs)) of @@ -238,21 +268,28 @@ handle_info({req_timedout, From}, State) -> {noreply, State}; {value, #request{stream_to = StreamTo, req_id = ReqId}} -> catch StreamTo ! {ibrowse_async_response_timeout, ReqId}, - shutting_down(State), - do_error_reply(State, req_timedout), - {stop, normal, State} + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + do_error_reply(State_1, req_timedout), + delayed_stop_timer(), + {noreply, State_1} end; handle_info(timeout, State) -> do_trace("Inactivity timeout triggered. Shutting down connection~n", []), - shutting_down(State), - do_error_reply(State, req_timedout), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + do_error_reply(State_1, req_timedout), + delayed_stop_timer(), + {noreply, State_1}; handle_info({trace, Bool}, State) -> put(my_trace_flag, Bool), {noreply, State}; +handle_info(delayed_stop, State) -> + {stop, normal, State}; + handle_info(Info, State) -> io:format("Unknown message recvd for ~1000.p:~1000.p -> ~p~n", [State#state.host, State#state.port, Info]), @@ -264,8 +301,10 @@ handle_info(Info, State) -> %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- -terminate(_Reason, State) -> +terminate(_Reason, #state{lb_ets_tid = Tid} = State) -> do_close(State), + shutting_down(State), + (catch ets:select_delete(Tid, [{{{'_','_','$1'},'_'},[{'==','$1',{const,self()}}],[true]}])), ok. %%-------------------------------------------------------------------- @@ -285,16 +324,20 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- handle_sock_data(Data, #state{status=idle}=State) -> do_trace("Data recvd on socket in state idle!. ~1000.p~n", [Data]), - shutting_down(State), - do_error_reply(State, data_in_status_idle), - do_close(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + do_error_reply(State_1, data_in_status_idle), + do_close(State_1), + delayed_stop_timer(), + {noreply, State_1}; handle_sock_data(Data, #state{status = get_header}=State) -> case parse_response(Data, State) of {error, _Reason} -> - shutting_down(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + delayed_stop_timer(), + {noreply, State_1}; #state{socket = Socket, status = Status, cur_req = CurReq} = State_1 -> _ = case {Status, CurReq} of {get_header, #request{caller_controls_socket = true}} -> @@ -315,10 +358,12 @@ handle_sock_data(Data, #state{status = get_body, true -> case accumulate_response(Data, State) of {error, Reason} -> - shutting_down(State), - fail_pipelined_requests(State, + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + fail_pipelined_requests(State_1, {error, {Reason, {stat_code, StatCode}, Headers}}), - {stop, normal, State}; + delayed_stop_timer(), + {noreply, State_1}; State_1 -> _ = active_once(State_1), State_2 = set_inac_timer(State_1), @@ -327,10 +372,12 @@ handle_sock_data(Data, #state{status = get_body, _ -> case parse_11_response(Data, State) of {error, Reason} -> - shutting_down(State), - fail_pipelined_requests(State, + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + fail_pipelined_requests(State_1, {error, {Reason, {stat_code, StatCode}, Headers}}), - {stop, normal, State}; + delayed_stop_timer(), + {noreply, State_1}; #state{cur_req = #request{caller_controls_socket = Ccs}, interim_reply_sent = Irs} = State_1 -> _ = case Irs of @@ -451,11 +498,11 @@ file_mode(_Srtf) -> write. %%-------------------------------------------------------------------- %% Handles the case when the server closes the socket %%-------------------------------------------------------------------- -handle_sock_closed(#state{status=get_header} = State) -> +handle_sock_closed(#state{status=get_header} = State, _) -> shutting_down(State), - do_error_reply(State, connection_closed); + do_error_reply(State, connection_closed_no_retry); -handle_sock_closed(#state{cur_req=undefined} = State) -> +handle_sock_closed(#state{cur_req=undefined} = State, _) -> shutting_down(State); %% We check for IsClosing because this the server could have sent a @@ -469,7 +516,7 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC recvd_headers = Headers, status_line = Status_line, raw_headers = Raw_headers - }=State) -> + }=State, Retry_state) -> #request{from=From, stream_to=StreamTo, req_id=ReqId, response_format = Resp_format, options = Options, @@ -497,10 +544,20 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC {ok, SC, Headers, Buf, Raw_req} end, State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply), - ok = do_error_reply(State_1#state{reqs = Reqs_1}, connection_closed), + case Retry_state of + ?dont_retry_pipelined_requests -> + ok = do_error_reply(State_1#state{reqs = Reqs_1}, connection_closed_no_retry); + ?can_retry_pipelined_requests -> + ok = do_error_reply(State_1#state{reqs = Reqs_1}, connection_closed) + end, State_1; _ -> - ok = do_error_reply(State, connection_closed), + case Retry_state of + ?dont_retry_pipelined_requests -> + ok = do_error_reply(State, connection_closed_no_retry); + ?can_retry_pipelined_requests -> + ok = do_error_reply(State, connection_closed) + end, State end. @@ -705,10 +762,12 @@ send_req_1(From, connect_timeout = Conn_timeout}, send_req_1(From, Url, Headers, Method, Body, Options, Timeout, State_3); Err -> - shutting_down(State_2), + State_3 = State_2#state{proc_state = ?dead_proc_walking}, + shutting_down(State_3), do_trace("Error connecting. Reason: ~1000.p~n", [Err]), gen_server:reply(From, {error, {conn_failed, Err}}), - {stop, normal, State_2} + delayed_stop_timer(), + {noreply, State_3} end; %% Send a CONNECT request. @@ -760,16 +819,20 @@ send_req_1(From, State_3 = set_inac_timer(State_2), {noreply, State_3}; Err -> - shutting_down(State_1), + State_2 = State_1#state{proc_state = ?dead_proc_walking}, + shutting_down(State_2), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State_1} + delayed_stop_timer(), + {noreply, State_2} end; Err -> - shutting_down(State_1), + State_2 = State_1#state{proc_state = ?dead_proc_walking}, + shutting_down(State_2), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State_1} + delayed_stop_timer(), + {noreply, State_2} end; send_req_1(From, Url, Headers, Method, Body, Options, Timeout, @@ -868,16 +931,20 @@ send_req_1(From, State_4 = set_inac_timer(State_3), {noreply, State_4}; Err -> - shutting_down(State), + State_2 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_2), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State} + delayed_stop_timer(), + {noreply, State_2} end; Err -> - shutting_down(State), + State_2 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_2), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State} + delayed_stop_timer(), + {noreply, State_2} end. maybe_modify_headers(#url{}, connect, _, Headers, State) -> @@ -1436,7 +1503,7 @@ handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId, _ -> {file, TmpFilename} end, - {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(RespHeaders, Raw_headers, Options), + {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(Status_line, RespHeaders, Raw_headers, Options), Give_raw_req = get_value(return_raw_request, Options, false), Reply = case get_value(give_raw_headers, Options, false) of true when Give_raw_req == false -> @@ -1463,7 +1530,7 @@ handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId, reply_buffer = RepBuf } = State) -> Body = RepBuf, - {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(Resp_headers, Raw_headers, Options), + {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(Status_line, Resp_headers, Raw_headers, Options), Give_raw_req = get_value(return_raw_request, Options, false), Reply = case get_value(give_raw_headers, Options, false) of true when Give_raw_req == false -> @@ -1768,7 +1835,7 @@ send_async_headers(ReqId, StreamTo, Give_raw_headers, recvd_headers = Headers, http_status_code = StatCode, cur_req = #request{options = Opts} }) -> - {Headers_1, Raw_headers_1} = maybe_add_custom_headers(Headers, Raw_headers, Opts), + {Headers_1, Raw_headers_1} = maybe_add_custom_headers(Status_line, Headers, Raw_headers, Opts), case Give_raw_headers of false -> catch StreamTo ! {ibrowse_async_headers, ReqId, StatCode, Headers_1}; @@ -1776,7 +1843,7 @@ send_async_headers(ReqId, StreamTo, Give_raw_headers, catch StreamTo ! {ibrowse_async_headers, ReqId, Status_line, Raw_headers_1} end. -maybe_add_custom_headers(Headers, Raw_headers, Opts) -> +maybe_add_custom_headers(Status_line, Headers, Raw_headers, Opts) -> Custom_headers = get_value(add_custom_headers, Opts, []), Headers_1 = Headers ++ Custom_headers, Raw_headers_1 = case Custom_headers of @@ -1786,7 +1853,12 @@ maybe_add_custom_headers(Headers, Raw_headers, Opts) -> _ -> Raw_headers end, - {Headers_1, Raw_headers_1}. + case get_value(preserve_status_line, Opts, false) of + true -> + {[{ibrowse_status_line, Status_line} | Headers_1], Raw_headers_1}; + false -> + {Headers_1, Raw_headers_1} + end. format_response_data(Resp_format, Body) -> case Resp_format of @@ -1935,7 +2007,7 @@ shutting_down(#state{lb_ets_tid = undefined}) -> ok; shutting_down(#state{lb_ets_tid = Tid, cur_pipeline_size = _Sz}) -> - catch ets:delete(Tid, self()). + (catch ets:select_delete(Tid, [{{{'_', '_', '$1'},'_'},[{'==','$1',{const,self()}}],[true]}])). inc_pipeline_counter(#state{is_closing = true} = State) -> State; @@ -1944,20 +2016,20 @@ inc_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> inc_pipeline_counter(#state{cur_pipeline_size = Pipe_sz} = State) -> State#state{cur_pipeline_size = Pipe_sz + 1}. -dec_pipeline_counter(#state{is_closing = true} = State) -> - State; -dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> - State; dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz, - lb_ets_tid = Tid} = State) -> - _ = try - ets:insert(Tid, {{Pipe_sz - 1, self()}, []}), - ets:delete(Tid, {Pipe_sz, self()}) - catch - _:_ -> - ok - end, - State#state{cur_pipeline_size = Pipe_sz - 1}. + lb_ets_tid = Tid, + proc_state = Proc_state} = State) when Tid /= undefined, + Proc_state /= ?dead_proc_walking -> + Ts = os:timestamp(), + (catch ets:select_delete(Tid, [{{{'_', '$2', '$1'},'_'}, + [{'==', '$1', {const,self()}}, + {'<', '$2', {const,Ts}} + ], + [true]}])), + catch ets:insert(Tid, {{Pipe_sz - 1, os:timestamp(), self()}, []}), + State#state{cur_pipeline_size = Pipe_sz - 1}; +dec_pipeline_counter(State) -> + State. flatten([H | _] = L) when is_integer(H) -> L; @@ -2042,3 +2114,6 @@ get_header_value(Name, Headers, Default_val) -> {value, {_, Val}} -> Val end. + +delayed_stop_timer() -> + erlang:send_after(500, self(), delayed_stop). From 141554c2144e4ef16321debe5dc5bddffb6a2085 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Fri, 8 Aug 2014 15:48:20 +0100 Subject: [PATCH 07/45] More fixes to pipelining --- test/ibrowse_test_server.erl | 80 +++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index 703227d..1d72210 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -15,25 +15,29 @@ start_server(Port, Sock_type) -> Fun = fun() -> - Name = server_proc_name(Port), - register(Name, self()), - case do_listen(Sock_type, Port, [{active, false}, - {reuseaddr, true}, - {nodelay, true}, - {packet, http}]) of - {ok, Sock} -> - do_trace("Server listening on port: ~p~n", [Port]), - accept_loop(Sock, Sock_type); - Err -> - erlang:error( + Proc_name = server_proc_name(Port), + case whereis(Proc_name) of + undefined -> + register(Proc_name, self()), + case do_listen(Sock_type, Port, [{active, false}, + {reuseaddr, true}, + {nodelay, true}, + {packet, http}]) of + {ok, Sock} -> + do_trace("Server listening on port: ~p~n", [Port]), + accept_loop(Sock, Sock_type); + Err -> + erlang:error( lists:flatten( - io_lib:format( - "Failed to start server on port ~p. ~p~n", - [Port, Err]))), - exit({listen_error, Err}) - end, - unregister(Name) - end, + io_lib:format( + "Failed to start server on port ~p. ~p~n", + [Port, Err]))), + exit({listen_error, Err}) + end; + _X -> + ok + end + end, spawn_link(Fun). stop_server(Port) -> @@ -88,12 +92,16 @@ server_loop(Sock, Sock_type, #request{headers = Headers} = Req) -> {http, Sock, {http_header, _, _, _, _} = H} -> server_loop(Sock, Sock_type, Req#request{headers = [H | Headers]}); {http, Sock, http_eoh} -> - process_request(Sock, Sock_type, Req), - server_loop(Sock, Sock_type, #request{}); + case process_request(Sock, Sock_type, Req) of + close_connection -> + gen_tcp:shutdown(Sock, read_write); + _ -> + server_loop(Sock, Sock_type, #request{}) + end; {http, Sock, {http_error, Err}} -> - do_trace("Error parsing HTTP request:~n" - "Req so far : ~p~n" - "Err : ", [Req, Err]), + io:format("Error parsing HTTP request:~n" + "Req so far : ~p~n" + "Err : ~p", [Req, Err]), exit({http_error, Err}); {setopts, Opts} -> setopts(Sock, Sock_type, Opts), @@ -104,9 +112,9 @@ server_loop(Sock, Sock_type, #request{headers = Headers} = Req) -> stop -> ok; Other -> - do_trace("Recvd unknown msg: ~p~n", [Other]), + io:format("Recvd unknown msg: ~p~n", [Other]), exit({unknown_msg, Other}) - after 5000 -> + after 120000 -> do_trace("Timing out client connection~n", []), ok end. @@ -145,7 +153,7 @@ process_request(Sock, Sock_type, headers = _Headers, uri = {abs_path, "/ibrowse_inac_timeout_test"}} = Req) -> do_trace("Recvd req: ~p. Sleeping for 30 secs...~n", [Req]), - timer:sleep(30000), + timer:sleep(3000), do_trace("...Sending response now.~n", []), Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>, do_send(Sock, Sock_type, Resp); @@ -178,7 +186,7 @@ process_request(Sock, Sock_type, #request{method='HEAD', headers = _Headers, uri = {abs_path, "/ibrowse_head_test"}}) -> - Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\nTransfer-Encoding: chunked\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>, + Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\Date: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>, do_send(Sock, Sock_type, Resp); process_request(Sock, Sock_type, #request{method='POST', @@ -192,10 +200,26 @@ process_request(Sock, Sock_type, uri = {abs_path, "/ibrowse_303_with_body_test"}}) -> Resp = <<"HTTP/1.1 303 See Other\r\nLocation: http://example.org\r\nContent-Length: 5\r\n\r\nabcde">>, do_send(Sock, Sock_type, Resp); +process_request(Sock, Sock_type, + #request{method='GET', + headers = _Headers, + uri = {abs_path, "/ibrowse_handle_one_request_only_with_delay"}}) -> + timer:sleep(2000), + Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>, + do_send(Sock, Sock_type, Resp), + close_connection; +process_request(Sock, Sock_type, + #request{method='GET', + headers = _Headers, + uri = {abs_path, "/ibrowse_handle_one_request_only"}}) -> + Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>, + do_send(Sock, Sock_type, Resp), + close_connection; process_request(Sock, Sock_type, Req) -> do_trace("Recvd req: ~p~n", [Req]), Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>, - do_send(Sock, Sock_type, Resp). + do_send(Sock, Sock_type, Resp), + timer:sleep(random:uniform(100)). do_send(Sock, tcp, Resp) -> gen_tcp:send(Sock, Resp); From edf810441cf5915d6754ff461e2c4427d20dba6c Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Fri, 8 Aug 2014 15:48:48 +0100 Subject: [PATCH 08/45] More fixes to pipelining --- test/ibrowse_test.erl | 152 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 123 insertions(+), 29 deletions(-) diff --git a/test/ibrowse_test.erl b/test/ibrowse_test.erl index 407ffcb..e216e82 100644 --- a/test/ibrowse_test.erl +++ b/test/ibrowse_test.erl @@ -8,9 +8,10 @@ load_test/3, send_reqs_1/3, do_send_req/2, + local_unit_tests/0, unit_tests/0, - unit_tests/1, - unit_tests_1/2, + unit_tests/2, + unit_tests_1/3, ue_test/0, ue_test/1, verify_chunked_streaming/0, @@ -34,9 +35,13 @@ test_303_response_with_a_body/1, test_binary_headers/0, test_binary_headers/1, - test_generate_body_0/0 + test_generate_body_0/0, + test_retry_of_requests/0, + test_retry_of_requests/1 ]). +-include("ibrowse.hrl"). + test_stream_once(Url, Method, Options) -> test_stream_once(Url, Method, Options, 5000). @@ -90,6 +95,8 @@ send_reqs_1(Url, NumWorkers, NumReqsPerWorker) -> ets:new(pid_table, [named_table, public]), ets:new(ibrowse_test_results, [named_table, public]), ets:new(ibrowse_errors, [named_table, public, ordered_set]), + ets:new(ibrowse_counter, [named_table, public, ordered_set]), + ets:insert(ibrowse_counter, {req_id, 1}), init_results(), process_flag(trap_exit, true), log_msg("Starting spawning of workers...~n", []), @@ -207,6 +214,16 @@ dump_errors(Key, Iod) -> %%------------------------------------------------------------------------------ %% Unit Tests %%------------------------------------------------------------------------------ +-define(LOCAL_TESTS, [ + {local_test_fun, test_20122010, []}, + {local_test_fun, test_pipeline_head_timeout, []}, + {local_test_fun, test_head_transfer_encoding, []}, + {local_test_fun, test_head_response_with_body, []}, + {local_test_fun, test_303_response_with_a_body, []}, + {local_test_fun, test_binary_headers, []}, + {local_test_fun, test_retry_of_requests, []} + ]). + -define(TEST_LIST, [{"http://intranet/messenger", get}, {"http://www.google.co.uk", get}, {"http://www.google.com", get}, @@ -236,27 +253,26 @@ dump_errors(Key, Iod) -> {"http://jigsaw.w3.org/HTTP/Basic/", get, [{basic_auth, {"guest", "guest"}}]}, {"http://jigsaw.w3.org/HTTP/CL/", get}, {"http://www.httpwatch.com/httpgallery/chunked/", get}, - {"https://github.com", get, [{ssl_options, [{depth, 2}]}]}, - {local_test_fun, test_20122010, []}, - {local_test_fun, test_pipeline_head_timeout, []}, - {local_test_fun, test_head_transfer_encoding, []}, - {local_test_fun, test_head_response_with_body, []}, - {local_test_fun, test_303_response_with_a_body, []}, - {local_test_fun, test_binary_headers, []} - ]). + {"https://github.com", get, [{ssl_options, [{depth, 2}]}]} + ] ++ ?LOCAL_TESTS). + +local_unit_tests() -> + error_logger:tty(false), + unit_tests([], ?LOCAL_TESTS), + error_logger:tty(true). unit_tests() -> - unit_tests([]). + unit_tests([], ?TEST_LIST). -unit_tests(Options) -> +unit_tests(Options, Test_list) -> application:start(crypto), application:start(public_key), application:start(ssl), (catch ibrowse_test_server:start_server(8181, tcp)), - ibrowse:start(), + application:start(ibrowse), Options_1 = Options ++ [{connect_timeout, 5000}], Test_timeout = proplists:get_value(test_timeout, Options, 60000), - {Pid, Ref} = erlang:spawn_monitor(?MODULE, unit_tests_1, [self(), Options_1]), + {Pid, Ref} = erlang:spawn_monitor(?MODULE, unit_tests_1, [self(), Options_1, Test_list]), receive {done, Pid} -> ok; @@ -269,14 +285,14 @@ unit_tests(Options) -> catch ibrowse_test_server:stop_server(8181), ok. -unit_tests_1(Parent, Options) -> +unit_tests_1(Parent, Options, Test_list) -> lists:foreach(fun({local_test_fun, Fun_name, Args}) -> execute_req(local_test_fun, Fun_name, Args); ({Url, Method}) -> execute_req(Url, Method, Options); ({Url, Method, X_Opts}) -> execute_req(Url, Method, X_Opts ++ Options) - end, ?TEST_LIST), + end, Test_list), Parent ! {done, self()}. verify_chunked_streaming() -> @@ -425,6 +441,7 @@ maybe_stream_next(Req_id, Options) -> end. execute_req(local_test_fun, Method, Args) -> + reset_ibrowse(), io:format(" ~-54.54w: ", [Method]), Result = (catch apply(?MODULE, Method, Args)), io:format("~p~n", [Result]); @@ -538,6 +555,74 @@ test_303_response_with_a_body(Url) -> {test_failed, Res} end. +%%------------------------------------------------------------------------------ +%% Test that retry of requests happens correctly, and that ibrowse doesn't retry +%% if there is not enough time left +%%------------------------------------------------------------------------------ +test_retry_of_requests() -> + clear_msg_q(), + test_retry_of_requests("http://localhost:8181/ibrowse_handle_one_request_only_with_delay"). + +test_retry_of_requests(Url) -> + Timeout_1 = 2050, + Res_1 = test_retry_of_requests(Url, Timeout_1), + case lists:filter(fun({_Pid, {ok, "200", _, _}}) -> + true; + (_) -> false + end, Res_1) of + [_|_] = X -> + Res_1_1 = Res_1 -- X, + case lists:all( + fun({_Pid, {error, retry_later}}) -> + true; + (_) -> + false + end, Res_1_1) of + true -> + ok; + false -> + exit({failed, Timeout_1, Res_1}) + end; + _ -> + exit({failed, Timeout_1, Res_1}) + end, + reset_ibrowse(), + Timeout_2 = 2200, + Res_2 = test_retry_of_requests(Url, Timeout_2), + case lists:filter(fun({_Pid, {ok, "200", _, _}}) -> + true; + (_) -> false + end, Res_2) of + [_|_] = Res_2_X -> + Res_2_1 = Res_2 -- Res_2_X, + case lists:all( + fun({_Pid, {error, X_err_2}}) -> + (X_err_2 == retry_later) orelse (X_err_2 == req_timedout); + (_) -> + false + end, Res_2_1) of + true -> + ok; + false -> + exit({failed, Timeout_2, Res_2}) + end; + _ -> + exit({failed, Timeout_2, Res_2}) + end, + success. + +test_retry_of_requests(Url, Timeout) -> + #url{host = Host, port = Port} = ibrowse_lib:parse_url(Url), + ibrowse:set_max_sessions(Host, Port, 1), + Parent = self(), + Pids = lists:map(fun(_) -> + spawn(fun() -> + Res = (catch ibrowse:send_req(Url, [], get, [], [], Timeout)), + Parent ! {self(), Res} + end) + end, lists:seq(1,10)), + accumulate_worker_resp(Pids). + %%------------------------------------------------------------------------------ %% Test what happens when the request at the head of a pipeline times out %%------------------------------------------------------------------------------ @@ -547,22 +632,27 @@ test_pipeline_head_timeout() -> test_pipeline_head_timeout(Url) -> {ok, Pid} = ibrowse:spawn_worker_process(Url), + Fixed_timeout = 2000, Test_parent = self(), Fun = fun({fixed, Timeout}) -> - spawn(fun() -> - do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout) - end); - (Timeout_mult) -> - spawn(fun() -> - Timeout = 1000 + Timeout_mult*1000, - do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout) - end) - end, - Pids = [Fun(X) || X <- [{fixed, 32000} | lists:seq(1,10)]], + X_pid = spawn(fun() -> + do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout) + end), + %% io:format("Pid ~p with a fixed timeout~n", [X_pid]), + X_pid; + (Timeout_mult) -> + Timeout = Fixed_timeout + Timeout_mult*1000, + X_pid = spawn(fun() -> + do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout) + end), + %% io:format("Pid ~p with a timeout of ~p~n", [X_pid, Timeout]), + X_pid + end, + Pids = [Fun(X) || X <- [{fixed, Fixed_timeout} | lists:seq(1,10)]], Result = accumulate_worker_resp(Pids), case lists:all(fun({_, X_res}) -> - X_res == {error,req_timedout} - end, Result) of + (X_res == {error,req_timedout}) orelse (X_res == {error, connection_closed}) + end, Result) of true -> success; false -> @@ -725,3 +815,7 @@ do_trace(true, Fmt, Args) -> io:format("~s -- " ++ Fmt, [ibrowse_lib:printable_date() | Args]); do_trace(_, _, _) -> ok. + +reset_ibrowse() -> + application:stop(ibrowse), + application:start(ibrowse). From 02b56cc1fae38919494f9d408db2aa9b16245923 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Fri, 8 Aug 2014 15:49:44 +0100 Subject: [PATCH 09/45] More fixes to pipelining --- test/ibrowse_load_test.erl | 220 +++++++++++++++++++++++++++---------- 1 file changed, 162 insertions(+), 58 deletions(-) diff --git a/test/ibrowse_load_test.erl b/test/ibrowse_load_test.erl index 3219314..5ff308e 100644 --- a/test/ibrowse_load_test.erl +++ b/test/ibrowse_load_test.erl @@ -1,77 +1,181 @@ -module(ibrowse_load_test). --export([go/3]). +-compile(export_all). --define(counters, ibrowse_load_test_counters). +-define(ibrowse_load_test_counters, ibrowse_load_test_counters). -go(URL, N_workers, N_reqs) -> - spawn(fun() -> - go_1(URL, N_workers, N_reqs) - end). +start(Num_workers, Num_requests, Max_sess) -> + proc_lib:spawn(fun() -> + start_1(Num_workers, Num_requests, Max_sess) + end). -go_1(URL, N_workers, N_reqs) -> - ets:new(?counters, [named_table, public]), +query_state() -> + ibrowse_load_test ! query_state. + +shutdown() -> + ibrowse_load_test ! shutdown. + +start_1(Num_workers, Num_requests, Max_sess) -> + register(ibrowse_load_test, self()), + application:start(ibrowse), + application:set_env(ibrowse, inactivity_timeout, 5000), + Ulimit = os:cmd("ulimit -n"), + case catch list_to_integer(string:strip(Ulimit, right, $\n)) of + X when is_integer(X), X > 3000 -> + ok; + X -> + io:format("Load test not starting. {insufficient_value_for_ulimit, ~p}~n", [X]), + exit({insufficient_value_for_ulimit, X}) + end, + ets:new(?ibrowse_load_test_counters, [named_table, public]), + ets:new(ibrowse_load_timings, [named_table, public]), try - ets:insert(?counters, [{success, 0}, - {failed, 0}, - {timeout, 0}, - {retry_later, 0}]), - Start_time = now(), - Pids = spawn_workers(N_workers, N_reqs, URL, self(), []), - wait_for_pids(Pids), - End_time = now(), - Time_taken = trunc(round(timer:now_diff(End_time, Start_time) / 1000000)), - [{_, Success_reqs}] = ets:lookup(?counters, success), - Total_reqs = N_workers*N_reqs, - Req_rate = case Time_taken > 0 of - true -> - trunc(Success_reqs / Time_taken); - false when Success_reqs == Total_reqs -> - withabix; - false -> - without_a_bix - end, - io:format("Stats : ~p~n", [ets:tab2list(?counters)]), - io:format("Total reqs : ~p~n", [Total_reqs]), - io:format("Time taken : ~p seconds~n", [Time_taken]), - io:format("Reqs / sec : ~p~n", [Req_rate]) - catch Class:Reason -> - io:format("Load test crashed. Reason: ~p~n" - "Stacktrace : ~p~n", - [{Class, Reason}, erlang:get_stacktrace()]) + ets:insert(?ibrowse_load_test_counters, [{success, 0}, + {failed, 0}, + {timeout, 0}, + {retry_later, 0}, + {one_request_only, 0} + ]), + ibrowse:set_max_sessions("localhost", 8081, Max_sess), + Start_time = now(), + Workers = spawn_workers(Num_workers, Num_requests), + erlang:send_after(1000, self(), print_diagnostics), + ok = wait_for_workers(Workers), + End_time = now(), + Time_in_secs = trunc(round(timer:now_diff(End_time, Start_time) / 1000000)), + Req_count = Num_workers * Num_requests, + [{_, Success_count}] = ets:lookup(?ibrowse_load_test_counters, success), + case Success_count == Req_count of + true -> + io:format("Test success. All requests succeeded~n", []); + false when Success_count > 0 -> + io:format("Test failed. Some successes~n", []); + false -> + io:format("Test failed. ALL requests FAILED~n", []) + end, + case Time_in_secs > 0 of + true -> + io:format("Reqs/sec achieved : ~p~n", [trunc(round(Success_count / Time_in_secs))]); + false -> + ok + end, + io:format("Load test results:~n~p~n", [ets:tab2list(?ibrowse_load_test_counters)]), + io:format("Timings: ~p~n", [calculate_timings()]) + catch Err -> + io:format("Err: ~p~n", [Err]) after - ets:delete(?counters) + ets:delete(?ibrowse_load_test_counters), + ets:delete(ibrowse_load_timings), + unregister(ibrowse_load_test) end. -spawn_workers(0, _, _, _, Acc) -> - Acc; -spawn_workers(N_workers, N_reqs, URL, Parent, Acc) -> - Pid = spawn(fun() -> - worker(N_reqs, URL, Parent) - end), - spawn_workers(N_workers - 1, N_reqs, URL, Parent, [Pid | Acc]). +calculate_timings() -> + {Max, Min, Mean} = get_mmv(ets:first(ibrowse_load_timings), {0, 9999999, 0}), + Variance = trunc(round(ets:foldl(fun({_, X}, X_acc) -> + (X - Mean)*(X-Mean) + X_acc + end, 0, ibrowse_load_timings) / ets:info(ibrowse_load_timings, size))), + Std_dev = trunc(round(math:sqrt(Variance))), + {ok, [{max, Max}, + {min, Min}, + {mean, Mean}, + {variance, Variance}, + {standard_deviation, Std_dev}]}. + +get_mmv('$end_of_table', {Max, Min, Total}) -> + Mean = trunc(round(Total / ets:info(ibrowse_load_timings, size))), + {Max, Min, Mean}; +get_mmv(Key, {Max, Min, Total}) -> + [{_, V}] = ets:lookup(ibrowse_load_timings, Key), + get_mmv(ets:next(ibrowse_load_timings, Key), {max(Max, V), min(Min, V), Total + V}). + + +spawn_workers(Num_w, Num_r) -> + spawn_workers(Num_w, Num_r, self(), []). -wait_for_pids([Pid | T]) -> +spawn_workers(0, _Num_requests, _Parent, Acc) -> + lists:reverse(Acc); +spawn_workers(Num_workers, Num_requests, Parent, Acc) -> + Pid_ref = spawn_monitor(fun() -> + random:seed(now()), + case catch worker_loop(Parent, Num_requests) of + {'EXIT', Rsn} -> + io:format("Worker crashed with reason: ~p~n", [Rsn]); + _ -> + ok + end + end), + spawn_workers(Num_workers - 1, Num_requests, Parent, [Pid_ref | Acc]). + +wait_for_workers([]) -> + ok; +wait_for_workers([{Pid, Pid_ref} | T] = Pids) -> receive {done, Pid} -> - wait_for_pids(T); + wait_for_workers(T); {done, Some_pid} -> - wait_for_pids([Pid | (T -- [Some_pid])]) - end; -wait_for_pids([]) -> - ok. - + wait_for_workers([{Pid, Pid_ref} | lists:keydelete(Some_pid, 1, T)]); + print_diagnostics -> + io:format("~1000.p~n", [ibrowse:get_metrics()]), + erlang:send_after(1000, self(), print_diagnostics), + wait_for_workers(Pids); + query_state -> + io:format("Waiting for ~p~n", [Pids]), + wait_for_workers(Pids); + shutdown -> + io:format("Shutting down on command. Still waiting for ~p workers~n", [length(Pids)]); + {'DOWN', _, process, _, normal} -> + wait_for_workers(Pids); + {'DOWN', _, process, Down_pid, Rsn} -> + io:format("Worker ~p died. Reason: ~p~n", [Down_pid, Rsn]), + wait_for_workers(lists:keydelete(Down_pid, 1, Pids)); + X -> + io:format("Recvd unknown msg: ~p~n", [X]), + wait_for_workers(Pids) + end. -worker(0, _, Parent) -> +worker_loop(Parent, 0) -> Parent ! {done, self()}; -worker(N, URL, Parent) -> - case ibrowse:send_req(URL, [], get) of +worker_loop(Parent, N) -> + Delay = random:uniform(100), + Url = case Delay rem 10 of + %% Change 10 to some number between 0-9 depending on how + %% much chaos you want to introduce into the server + %% side. The higher the number, the more often the + %% server will close a connection after serving the + %% first request, thereby forcing the client to + %% retry. Any number of 10 or higher will disable this + %% chaos mechanism + 10 -> + ets:update_counter(?ibrowse_load_test_counters, one_request_only, 1), + "http://localhost:8081/ibrowse_handle_one_request_only"; + _ -> + "http://localhost:8081/blah" + end, + Start_time = now(), + Res = ibrowse:send_req(Url, [], get), + End_time = now(), + Time_taken = trunc(round(timer:now_diff(End_time, Start_time) / 1000)), + ets:insert(ibrowse_load_timings, {now(), Time_taken}), + case Res of {ok, "200", _, _} -> - ets:update_counter(?counters, success, 1); + ets:update_counter(?ibrowse_load_test_counters, success, 1); {error, req_timedout} -> - ets:update_counter(?counters, timeout, 1); + ets:update_counter(?ibrowse_load_test_counters, timeout, 1); {error, retry_later} -> - ets:update_counter(?counters, retry_later, 1); + ets:update_counter(?ibrowse_load_test_counters, retry_later, 1); + {error, Reason} -> + update_unknown_counter(Reason, 1); _ -> - ets:update_counter(?counters, failed, 1) + io:format("~p -- Res: ~p~n", [self(), Res]), + ets:update_counter(?ibrowse_load_test_counters, failed, 1) end, - worker(N - 1, URL, Parent). + timer:sleep(Delay), + worker_loop(Parent, N - 1). + +update_unknown_counter(Counter, Inc_val) -> + case catch ets:update_counter(?ibrowse_load_test_counters, Counter, Inc_val) of + {'EXIT', _} -> + ets:insert_new(?ibrowse_load_test_counters, {Counter, 0}), + update_unknown_counter(Counter, Inc_val); + _ -> + ok + end. From f19968df94ff4e4f918b724a2d5c19248db204a3 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Fri, 8 Aug 2014 15:45:51 +0100 Subject: [PATCH 10/45] More fixes to pipelining --- src/ibrowse.erl | 132 ++++++++++++++------- src/ibrowse_http_client.erl | 215 +++++++++++++++++++++++----------- src/ibrowse_lb.erl | 84 ++++++------- src/ibrowse_lib.erl | 10 +- test/ibrowse_load_test.erl | 220 ++++++++++++++++++++++++++--------- test/ibrowse_test.erl | 152 +++++++++++++++++++----- test/ibrowse_test_server.erl | 80 ++++++++----- 7 files changed, 612 insertions(+), 281 deletions(-) diff --git a/src/ibrowse.erl b/src/ibrowse.erl index 3d62224..6c87364 100644 --- a/src/ibrowse.erl +++ b/src/ibrowse.erl @@ -158,7 +158,7 @@ stop() -> %% respHeader() = {headerName(), headerValue()} %% headerName() = string() %% headerValue() = string() -%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason} +%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason} %% req_id() = term() %% ResponseBody = string() | {file, Filename} %% Reason = term() @@ -252,6 +252,11 @@ send_req(Url, Headers, Method, Body) -> %% headers. Not quite sure why someone would want this, but one of my %% users asked for it, so here it is.
  • %% +%%
  • The preserve_status_line option is to get the raw status line as a custom header +%% in the response. The status line is returned as a tuple {ibrowse_status_line, Status_line_binary} +%% If both the give_raw_headers and preserve_status_line are specified +%% in a request, only the give_raw_headers is honoured.
  • +%% %%
  • The preserve_chunked_encoding option enables the caller %% to receive the raw data stream when the Transfer-Encoding of the server %% response is Chunked. @@ -336,7 +341,7 @@ send_req(Url, Headers, Method, Body, Options, Timeout) -> Max_sessions, Max_pipeline_size, {SSLOptions, IsSSL}, - Headers, Method, Body, Options_1, Timeout, 0); + Headers, Method, Body, Options_1, Timeout, Timeout, os:timestamp(), 0); Err -> {error, {url_parsing_failed, Err}} end. @@ -345,29 +350,41 @@ try_routing_request(Lb_pid, Parsed_url, Max_sessions, Max_pipeline_size, {SSLOptions, IsSSL}, - Headers, Method, Body, Options_1, Timeout, Try_count) when Try_count < 3 -> + Headers, Method, Body, Options_1, Timeout, + Ori_timeout, Req_start_time, Try_count) when Try_count =< 3 -> ProcessOptions = get_value(worker_process_options, Options_1, []), case ibrowse_lb:spawn_connection(Lb_pid, Parsed_url, Max_sessions, Max_pipeline_size, {SSLOptions, IsSSL}, ProcessOptions) of - {ok, Conn_Pid} -> + {ok, {_Pid_cur_spec_size, _, Conn_Pid}} -> case do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options_1, Timeout) of {error, sel_conn_closed} -> - try_routing_request(Lb_pid, Parsed_url, - Max_sessions, - Max_pipeline_size, - {SSLOptions, IsSSL}, - Headers, Method, Body, Options_1, Timeout, Try_count + 1); + Time_now = os:timestamp(), + Time_taken_so_far = trunc(round(timer:now_diff(Time_now, Req_start_time)/1000)), + Time_remaining = Ori_timeout - Time_taken_so_far, + Time_remaining_percent = trunc(round((Time_remaining/Ori_timeout)*100)), + %% io:format("~p -- Time_remaining: ~p (~p%)~n", [self(), Time_remaining, Time_remaining_percent]), + case (Time_remaining > 0) andalso (Time_remaining_percent >= 5) of + true -> + try_routing_request(Lb_pid, Parsed_url, + Max_sessions, + Max_pipeline_size, + {SSLOptions, IsSSL}, + Headers, Method, Body, Options_1, + Time_remaining, Ori_timeout, Req_start_time, Try_count + 1); + false -> + {error, retry_later} + end; Res -> Res end; Err -> Err end; -try_routing_request(_, _, _, _, _, _, _, _, _, _, _) -> +try_routing_request(_, _, _, _, _, _, _, _, _, _, _, _, _) -> {error, retry_later}. merge_options(Host, Port, Options) -> @@ -441,14 +458,29 @@ do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout) -> Headers, Method, ensure_bin(Body), Options, Timeout) of {'EXIT', {timeout, _}} -> + P_info = case catch erlang:process_info(Conn_Pid, [messages, message_queue_len, backtrace]) of + [_|_] = Conn_Pid_info_list -> + Conn_Pid_info_list; + _ -> + process_info_not_available + end, + (catch lager:error("{ibrowse_http_client, send_req, ~1000.p} gen_server call timeout.~nProcess info: ~p~n", + [[Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout], P_info])), {error, req_timedout}; - {'EXIT', {noproc, {gen_server, call, [Conn_Pid, _, _]}}} -> - {error, sel_conn_closed}; - {'EXIT', {normal, _}} -> - {error, sel_conn_closed}; - {'EXIT', {connection_closed, _}} -> + {'EXIT', {normal, _}} = Ex_rsn -> + (catch lager:error("{ibrowse_http_client, send_req, ~1000.p} gen_server call got ~1000.p~n", + [[Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout], Ex_rsn])), + {error, req_timedout}; + {error, X} when X == connection_closed; + X == {send_failed, {error, enotconn}}; + X == {send_failed,{error,einval}}; + X == {send_failed,{error,closed}}; + X == connection_closing; + ((X == connection_closed_no_retry) andalso ((Method == get) orelse (Method == head))) -> {error, sel_conn_closed}; - {error, connection_closed} -> + {error, connection_closed_no_retry} -> + {error, connection_closed}; + {error, {'EXIT', {noproc, _}}} -> {error, sel_conn_closed}; {'EXIT', Reason} -> {error, {'EXIT', Reason}}; @@ -637,28 +669,39 @@ show_dest_status(Url) -> %% included. show_dest_status(Host, Port) -> case get_metrics(Host, Port) of - {Lb_pid, MsgQueueSize, Tid, Size, - {{First_p_sz, _}, - {Last_p_sz, _}}} -> + {Lb_pid, MsgQueueSize, + Tid, Size, + {{First_p_sz, First_p_sz}, + {Last_p_sz, Last_p_sz}}} -> io:format("Load Balancer Pid : ~p~n" "LB process msg q size : ~p~n" "LB ETS table id : ~p~n" "Num Connections : ~p~n" - "Smallest pipeline : ~p:~p~n" - "Largest pipeline : ~p:~p~n", + "Smallest pipeline : ~p~n" + "Largest pipeline : ~p~n", [Lb_pid, MsgQueueSize, Tid, Size, - First_p_sz, First_p_sz, - Last_p_sz, Last_p_sz - ]); + First_p_sz, Last_p_sz]); _Err -> io:format("Metrics not available~n", []) end. get_metrics() -> - [get_metrics(Host, Port) || #lb_pid{host_port = {Host, Port}} <- - ets:tab2list(ibrowse_lb), - is_list(Host), - is_integer(Port)]. + Dests = lists:filter( + fun(#lb_pid{host_port = {Host, Port}}) when is_list(Host), + is_integer(Port) -> + true; + (_) -> + false + end, ets:tab2list(ibrowse_lb)), + lists:foldl( + fun(#lb_pid{host_port = {X_host, X_port}}, X_acc) -> + case get_metrics(X_host, X_port) of + {_, _, _, _, _} = X_res -> + [X_res | X_acc]; + _X_res -> + X_acc + end + end, [], Dests). get_metrics(Host, Port) -> case ets:lookup(ibrowse_lb, {Host, Port}) of @@ -666,20 +709,25 @@ get_metrics(Host, Port) -> no_active_processes; [#lb_pid{pid = Lb_pid, ets_tid = Tid}] -> MsgQueueSize = (catch process_info(Lb_pid, message_queue_len)), - try - Size = ets:info(Tid, size), - case Size of - 0 -> - ok; - _ -> - {First_p_sz, _} = ets:first(Tid), - {Last_p_sz, _} = ets:last(Tid), - {Lb_pid, MsgQueueSize, Tid, Size, - {{First_p_sz, First_p_sz}, - {Last_p_sz, Last_p_sz}}} - end - catch _:_ -> - not_available + case Tid of + undefined -> + {Lb_pid, MsgQueueSize, undefined, 0, {{0, 0}, {0, 0}}}; + _ -> + try + Size = ets:info(Tid, size), + case Size of + 0 -> + {Lb_pid, MsgQueueSize, undefined, 0, {{0, 0}, {0, 0}}}; + _ -> + {First_p_sz, _, _} = ets:first(Tid), + {Last_p_sz, _, _} = ets:last(Tid), + {Lb_pid, MsgQueueSize, + Tid, Size, + {{First_p_sz, First_p_sz}, {Last_p_sz, Last_p_sz}}} + end + catch _:_Err -> + not_available + end end end. diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index 04797e9..db9559a 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -53,7 +53,8 @@ deleted_crlf = false, transfer_encoding, chunk_size, chunk_size_buffer = <<>>, recvd_chunk_size, interim_reply_sent = false, - lb_ets_tid, cur_pipeline_size = 0, prev_req_id + lb_ets_tid, cur_pipeline_size = 0, prev_req_id, + proc_state }). -record(request, {url, method, options, from, @@ -73,6 +74,12 @@ -define(DEFAULT_STREAM_CHUNK_SIZE, 1024*1024). -define(dec2hex(X), erlang:integer_to_list(X, 16)). + +%% Macros to prevent spelling mistakes causing bugs +-define(dont_retry_pipelined_requests, dont_retry_pipelined_requests). +-define(can_retry_pipelined_requests, can_retry_pipelined_requests). +-define(dead_proc_walking, dead_proc_walking). + %%==================================================================== %% External functions %%==================================================================== @@ -102,9 +109,15 @@ stop(Conn_pid) -> end. send_req(Conn_Pid, Url, Headers, Method, Body, Options, Timeout) -> - gen_server:call( - Conn_Pid, - {send_req, {Url, Headers, Method, Body, Options, Timeout}}, Timeout). + case catch gen_server:call(Conn_Pid, + {send_req, {Url, Headers, Method, Body, Options, Timeout}}, Timeout) of + {'EXIT', {timeout, _}} -> + {error, req_timedout}; + {'EXIT', {noproc, _}} -> + {error, connection_closed}; + Res -> + Res + end. %%==================================================================== %% Server functions @@ -119,6 +132,7 @@ send_req(Conn_Pid, Url, Headers, Method, Body, Options, Timeout) -> %% {stop, Reason} %%-------------------------------------------------------------------- init({Lb_Tid, #url{host = Host, port = Port}, {SSLOptions, Is_ssl}}) -> + process_flag(trap_exit, true), State = #state{host = Host, port = Port, ssl_options = SSLOptions, @@ -128,6 +142,7 @@ init({Lb_Tid, #url{host = Host, port = Port}, {SSLOptions, Is_ssl}}) -> put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)), {ok, set_inac_timer(State)}; init(Url) when is_list(Url) -> + process_flag(trap_exit, true), case catch ibrowse_lib:parse_url(Url) of #url{protocol = Protocol} = Url_rec -> init({undefined, Url_rec, {[], Protocol == https}}); @@ -135,6 +150,7 @@ init(Url) when is_list(Url) -> {error, invalid_url} end; init({Host, Port}) -> + process_flag(trap_exit, true), State = #state{host = Host, port = Port}, put(ibrowse_trace_token, [Host, $:, integer_to_list(Port)]), @@ -156,6 +172,10 @@ init({Host, Port}) -> handle_call({send_req, _}, _From, #state{is_closing = true} = State) -> {reply, {error, connection_closing}, State}; +handle_call({send_req, _}, _From, #state{proc_state = ?dead_proc_walking} = State) -> + shutting_down(State), + {reply, {error, connection_closing}, State}; + handle_call({send_req, {Url, Headers, Method, Body, Options, Timeout}}, From, State) -> send_req_1(From, Url, Headers, Method, Body, Options, Timeout, State); @@ -207,30 +227,40 @@ handle_info({stream_next, _Req_id}, State) -> {noreply, State}; handle_info({stream_close, _Req_id}, State) -> - shutting_down(State), - do_close(State), - do_error_reply(State, closing_on_request), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + do_close(State_1), + do_error_reply(State_1, closing_on_request), + delayed_stop_timer(), + {noreply, State_1}; handle_info({tcp_closed, _Sock}, State) -> do_trace("TCP connection closed by peer!~n", []), - handle_sock_closed(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + handle_sock_closed(State_1, ?can_retry_pipelined_requests), + delayed_stop_timer(), + {noreply, State_1}; handle_info({ssl_closed, _Sock}, State) -> do_trace("SSL connection closed by peer!~n", []), - handle_sock_closed(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + handle_sock_closed(State_1, ?can_retry_pipelined_requests), + delayed_stop_timer(), + {noreply, State_1}; handle_info({tcp_error, _Sock, Reason}, State) -> do_trace("Error on connection to ~1000.p:~1000.p -> ~1000.p~n", [State#state.host, State#state.port, Reason]), - handle_sock_closed(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + handle_sock_closed(State_1, ?dont_retry_pipelined_requests), + delayed_stop_timer(), + {noreply, State_1}; handle_info({ssl_error, _Sock, Reason}, State) -> do_trace("Error on SSL connection to ~1000.p:~1000.p -> ~1000.p~n", [State#state.host, State#state.port, Reason]), - handle_sock_closed(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + handle_sock_closed(State_1, ?dont_retry_pipelined_requests), + delayed_stop_timer(), + {noreply, State_1}; handle_info({req_timedout, From}, State) -> case lists:keysearch(From, #request.from, queue:to_list(State#state.reqs)) of @@ -238,21 +268,28 @@ handle_info({req_timedout, From}, State) -> {noreply, State}; {value, #request{stream_to = StreamTo, req_id = ReqId}} -> catch StreamTo ! {ibrowse_async_response_timeout, ReqId}, - shutting_down(State), - do_error_reply(State, req_timedout), - {stop, normal, State} + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + do_error_reply(State_1, req_timedout), + delayed_stop_timer(), + {noreply, State_1} end; handle_info(timeout, State) -> do_trace("Inactivity timeout triggered. Shutting down connection~n", []), - shutting_down(State), - do_error_reply(State, req_timedout), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + do_error_reply(State_1, req_timedout), + delayed_stop_timer(), + {noreply, State_1}; handle_info({trace, Bool}, State) -> put(my_trace_flag, Bool), {noreply, State}; +handle_info(delayed_stop, State) -> + {stop, normal, State}; + handle_info(Info, State) -> io:format("Unknown message recvd for ~1000.p:~1000.p -> ~p~n", [State#state.host, State#state.port, Info]), @@ -264,8 +301,10 @@ handle_info(Info, State) -> %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- -terminate(_Reason, State) -> +terminate(_Reason, #state{lb_ets_tid = Tid} = State) -> do_close(State), + shutting_down(State), + (catch ets:select_delete(Tid, [{{{'_','_','$1'},'_'},[{'==','$1',{const,self()}}],[true]}])), ok. %%-------------------------------------------------------------------- @@ -285,16 +324,20 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- handle_sock_data(Data, #state{status=idle}=State) -> do_trace("Data recvd on socket in state idle!. ~1000.p~n", [Data]), - shutting_down(State), - do_error_reply(State, data_in_status_idle), - do_close(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + do_error_reply(State_1, data_in_status_idle), + do_close(State_1), + delayed_stop_timer(), + {noreply, State_1}; handle_sock_data(Data, #state{status = get_header}=State) -> case parse_response(Data, State) of {error, _Reason} -> - shutting_down(State), - {stop, normal, State}; + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + delayed_stop_timer(), + {noreply, State_1}; #state{socket = Socket, status = Status, cur_req = CurReq} = State_1 -> _ = case {Status, CurReq} of {get_header, #request{caller_controls_socket = true}} -> @@ -315,10 +358,12 @@ handle_sock_data(Data, #state{status = get_body, true -> case accumulate_response(Data, State) of {error, Reason} -> - shutting_down(State), - fail_pipelined_requests(State, + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + fail_pipelined_requests(State_1, {error, {Reason, {stat_code, StatCode}, Headers}}), - {stop, normal, State}; + delayed_stop_timer(), + {noreply, State_1}; State_1 -> _ = active_once(State_1), State_2 = set_inac_timer(State_1), @@ -327,10 +372,12 @@ handle_sock_data(Data, #state{status = get_body, _ -> case parse_11_response(Data, State) of {error, Reason} -> - shutting_down(State), - fail_pipelined_requests(State, + State_1 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_1), + fail_pipelined_requests(State_1, {error, {Reason, {stat_code, StatCode}, Headers}}), - {stop, normal, State}; + delayed_stop_timer(), + {noreply, State_1}; #state{cur_req = #request{caller_controls_socket = Ccs}, interim_reply_sent = Irs} = State_1 -> _ = case Irs of @@ -451,11 +498,11 @@ file_mode(_Srtf) -> write. %%-------------------------------------------------------------------- %% Handles the case when the server closes the socket %%-------------------------------------------------------------------- -handle_sock_closed(#state{status=get_header} = State) -> +handle_sock_closed(#state{status=get_header} = State, _) -> shutting_down(State), - do_error_reply(State, connection_closed); + do_error_reply(State, connection_closed_no_retry); -handle_sock_closed(#state{cur_req=undefined} = State) -> +handle_sock_closed(#state{cur_req=undefined} = State, _) -> shutting_down(State); %% We check for IsClosing because this the server could have sent a @@ -469,7 +516,7 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC recvd_headers = Headers, status_line = Status_line, raw_headers = Raw_headers - }=State) -> + }=State, Retry_state) -> #request{from=From, stream_to=StreamTo, req_id=ReqId, response_format = Resp_format, options = Options, @@ -497,10 +544,20 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC {ok, SC, Headers, Buf, Raw_req} end, State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply), - ok = do_error_reply(State_1#state{reqs = Reqs_1}, connection_closed), + case Retry_state of + ?dont_retry_pipelined_requests -> + ok = do_error_reply(State_1#state{reqs = Reqs_1}, connection_closed_no_retry); + ?can_retry_pipelined_requests -> + ok = do_error_reply(State_1#state{reqs = Reqs_1}, connection_closed) + end, State_1; _ -> - ok = do_error_reply(State, connection_closed), + case Retry_state of + ?dont_retry_pipelined_requests -> + ok = do_error_reply(State, connection_closed_no_retry); + ?can_retry_pipelined_requests -> + ok = do_error_reply(State, connection_closed) + end, State end. @@ -705,10 +762,12 @@ send_req_1(From, connect_timeout = Conn_timeout}, send_req_1(From, Url, Headers, Method, Body, Options, Timeout, State_3); Err -> - shutting_down(State_2), + State_3 = State_2#state{proc_state = ?dead_proc_walking}, + shutting_down(State_3), do_trace("Error connecting. Reason: ~1000.p~n", [Err]), gen_server:reply(From, {error, {conn_failed, Err}}), - {stop, normal, State_2} + delayed_stop_timer(), + {noreply, State_3} end; %% Send a CONNECT request. @@ -760,16 +819,20 @@ send_req_1(From, State_3 = set_inac_timer(State_2), {noreply, State_3}; Err -> - shutting_down(State_1), + State_2 = State_1#state{proc_state = ?dead_proc_walking}, + shutting_down(State_2), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State_1} + delayed_stop_timer(), + {noreply, State_2} end; Err -> - shutting_down(State_1), + State_2 = State_1#state{proc_state = ?dead_proc_walking}, + shutting_down(State_2), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State_1} + delayed_stop_timer(), + {noreply, State_2} end; send_req_1(From, Url, Headers, Method, Body, Options, Timeout, @@ -868,16 +931,20 @@ send_req_1(From, State_4 = set_inac_timer(State_3), {noreply, State_4}; Err -> - shutting_down(State), + State_2 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_2), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State} + delayed_stop_timer(), + {noreply, State_2} end; Err -> - shutting_down(State), + State_2 = State#state{proc_state = ?dead_proc_walking}, + shutting_down(State_2), do_trace("Send failed... Reason: ~p~n", [Err]), gen_server:reply(From, {error, {send_failed, Err}}), - {stop, normal, State} + delayed_stop_timer(), + {noreply, State_2} end. maybe_modify_headers(#url{}, connect, _, Headers, State) -> @@ -1436,7 +1503,7 @@ handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId, _ -> {file, TmpFilename} end, - {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(RespHeaders, Raw_headers, Options), + {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(Status_line, RespHeaders, Raw_headers, Options), Give_raw_req = get_value(return_raw_request, Options, false), Reply = case get_value(give_raw_headers, Options, false) of true when Give_raw_req == false -> @@ -1463,7 +1530,7 @@ handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId, reply_buffer = RepBuf } = State) -> Body = RepBuf, - {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(Resp_headers, Raw_headers, Options), + {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(Status_line, Resp_headers, Raw_headers, Options), Give_raw_req = get_value(return_raw_request, Options, false), Reply = case get_value(give_raw_headers, Options, false) of true when Give_raw_req == false -> @@ -1768,7 +1835,7 @@ send_async_headers(ReqId, StreamTo, Give_raw_headers, recvd_headers = Headers, http_status_code = StatCode, cur_req = #request{options = Opts} }) -> - {Headers_1, Raw_headers_1} = maybe_add_custom_headers(Headers, Raw_headers, Opts), + {Headers_1, Raw_headers_1} = maybe_add_custom_headers(Status_line, Headers, Raw_headers, Opts), case Give_raw_headers of false -> catch StreamTo ! {ibrowse_async_headers, ReqId, StatCode, Headers_1}; @@ -1776,7 +1843,7 @@ send_async_headers(ReqId, StreamTo, Give_raw_headers, catch StreamTo ! {ibrowse_async_headers, ReqId, Status_line, Raw_headers_1} end. -maybe_add_custom_headers(Headers, Raw_headers, Opts) -> +maybe_add_custom_headers(Status_line, Headers, Raw_headers, Opts) -> Custom_headers = get_value(add_custom_headers, Opts, []), Headers_1 = Headers ++ Custom_headers, Raw_headers_1 = case Custom_headers of @@ -1786,7 +1853,12 @@ maybe_add_custom_headers(Headers, Raw_headers, Opts) -> _ -> Raw_headers end, - {Headers_1, Raw_headers_1}. + case get_value(preserve_status_line, Opts, false) of + true -> + {[{ibrowse_status_line, Status_line} | Headers_1], Raw_headers_1}; + false -> + {Headers_1, Raw_headers_1} + end. format_response_data(Resp_format, Body) -> case Resp_format of @@ -1935,7 +2007,7 @@ shutting_down(#state{lb_ets_tid = undefined}) -> ok; shutting_down(#state{lb_ets_tid = Tid, cur_pipeline_size = _Sz}) -> - catch ets:delete(Tid, self()). + (catch ets:select_delete(Tid, [{{{'_', '_', '$1'},'_'},[{'==','$1',{const,self()}}],[true]}])). inc_pipeline_counter(#state{is_closing = true} = State) -> State; @@ -1944,20 +2016,20 @@ inc_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> inc_pipeline_counter(#state{cur_pipeline_size = Pipe_sz} = State) -> State#state{cur_pipeline_size = Pipe_sz + 1}. -dec_pipeline_counter(#state{is_closing = true} = State) -> - State; -dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> - State; dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz, - lb_ets_tid = Tid} = State) -> - _ = try - ets:insert(Tid, {{Pipe_sz - 1, self()}, []}), - ets:delete(Tid, {Pipe_sz, self()}) - catch - _:_ -> - ok - end, - State#state{cur_pipeline_size = Pipe_sz - 1}. + lb_ets_tid = Tid, + proc_state = Proc_state} = State) when Tid /= undefined, + Proc_state /= ?dead_proc_walking -> + Ts = os:timestamp(), + (catch ets:select_delete(Tid, [{{{'_', '$2', '$1'},'_'}, + [{'==', '$1', {const,self()}}, + {'<', '$2', {const,Ts}} + ], + [true]}])), + catch ets:insert(Tid, {{Pipe_sz - 1, os:timestamp(), self()}, []}), + State#state{cur_pipeline_size = Pipe_sz - 1}; +dec_pipeline_counter(State) -> + State. flatten([H | _] = L) when is_integer(H) -> L; @@ -2042,3 +2114,6 @@ get_header_value(Name, Headers, Default_val) -> {value, {_, Val}} -> Val end. + +delayed_stop_timer() -> + erlang:send_after(500, self(), delayed_stop). diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 2b34ddb..a802f87 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -36,7 +36,6 @@ port, max_sessions, max_pipeline_size, - num_cur_sessions = 0, proc_state }). @@ -123,24 +122,23 @@ handle_call(stop, _From, #state{ets_tid = Tid} = State) -> handle_call(_, _From, #state{proc_state = shutting_down} = State) -> {reply, {error, shutting_down}, State}; -%% Update max_sessions in #state with supplied value -handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _, _}, _From, - #state{num_cur_sessions = Num} = State) - when Num >= Max_sess -> - State_1 = maybe_create_ets(State), - Reply = find_best_connection(State_1#state.ets_tid, Max_pipe), - {reply, Reply, State_1#state{max_sessions = Max_sess, - max_pipeline_size = Max_pipe}}; - handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From, - #state{num_cur_sessions = Cur} = State) -> - State_1 = maybe_create_ets(State), - Tid = State_1#state.ets_tid, - {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options), - ets:insert(Tid, {{0, Pid}, []}), - {reply, {ok, Pid}, State_1#state{num_cur_sessions = Cur + 1, - max_sessions = Max_sess, - max_pipeline_size = Max_pipe}}; + State) -> + State_1 = maybe_create_ets(State), + Tid = State_1#state.ets_tid, + Tid_size = ets:info(Tid, size), + case Tid_size > Max_sess of + true -> + Reply = find_best_connection(Tid, Max_pipe, Tid_size), + {reply, Reply, State_1#state{max_sessions = Max_sess, + max_pipeline_size = Max_pipe}}; + false -> + {ok, Pid} = ibrowse_http_client:start({Tid, Url, SSL_options}, Process_options), + Ts = os:timestamp(), + ets:insert(Tid, {{0, Ts, Pid}, []}), + {reply, {ok, {0, Ts, Pid}}, State_1#state{max_sessions = Max_sess, + max_pipeline_size = Max_pipe}} + end; handle_call(Request, _From, State) -> Reply = {unknown_request, Request}, @@ -163,24 +161,6 @@ handle_cast(_Msg, State) -> %% {noreply, State, Timeout} | %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- -handle_info({'EXIT', Parent, _Reason}, #state{parent_pid = Parent} = State) -> - {stop, normal, State}; - -handle_info({'EXIT', _Pid, _Reason}, #state{ets_tid = undefined} = State) -> - {noreply, State}; - -handle_info({'EXIT', Pid, _Reason}, - #state{num_cur_sessions = Cur, - ets_tid = Tid} = State) -> - ets:match_delete(Tid, {{'_', Pid}, '_'}), - Cur_1 = Cur - 1, - case Cur_1 of - 0 -> - ets:delete(Tid), - {noreply, State#state{ets_tid = undefined, num_cur_sessions = 0}, 10000}; - _ -> - {noreply, State#state{num_cur_sessions = Cur_1}} - end; handle_info({trace, Bool}, #state{ets_tid = undefined} = State) -> put(my_trace_flag, Bool), @@ -216,7 +196,8 @@ handle_info(_Info, State) -> %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- -terminate(_Reason, _State) -> +terminate(_Reason, #state{host = Host, port = Port} = _State) -> + catch ets:delete(ibrowse_lb, {Host, Port}), ok. %%-------------------------------------------------------------------- @@ -230,23 +211,24 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -find_best_connection(Tid, Max_pipe) -> - First = ets:first(Tid), - case First of - {Pid_pipeline_size, Pid} when Pid_pipeline_size < Max_pipe -> - ets:delete(Tid, First), - ets:insert(Tid, {{Pid_pipeline_size, Pid}, []}), - {ok, Pid}; - _ -> - {error, retry_later} +find_best_connection(Tid, Max_pipe, _Num_cur) -> + case ets:first(Tid) of + {Spec_size, Ts, Pid} = First -> + case Spec_size >= Max_pipe of + true -> + {error, retry_later}; + false -> + ets:delete(Tid, First), + ets:insert(Tid, {{Spec_size + 1, Ts, Pid}, []}), + {ok, First} + end; + '$end_of_table' -> + {error, retry_later} end. -maybe_create_ets(#state{ets_tid = undefined, - host = Host, port = Port} = State) -> +maybe_create_ets(#state{ets_tid = undefined, host = Host, port = Port} = State) -> Tid = ets:new(ibrowse_lb, [public, ordered_set]), - ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, - pid = self(), - ets_tid = Tid}), + ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, pid = self(), ets_tid = Tid}), State#state{ets_tid = Tid}; maybe_create_ets(State) -> State. diff --git a/src/ibrowse_lib.erl b/src/ibrowse_lib.erl index 1ce6bd4..1098b0f 100644 --- a/src/ibrowse_lib.erl +++ b/src/ibrowse_lib.erl @@ -28,7 +28,8 @@ get_value/2, get_value/3, parse_url/1, - printable_date/0 + printable_date/0, + printable_date/1 ]). get_trace_status(Host, Port) -> @@ -367,8 +368,11 @@ default_port(https) -> 443; default_port(ftp) -> 21. printable_date() -> - {{Y,Mo,D},{H, M, S}} = calendar:local_time(), - {_,_,MicroSecs} = now(), + printable_date(os:timestamp()). + +printable_date(Now) -> + {{Y,Mo,D},{H, M, S}} = calendar:now_to_local_time(Now), + {_,_,MicroSecs} = Now, [integer_to_list(Y), $-, integer_to_list(Mo), diff --git a/test/ibrowse_load_test.erl b/test/ibrowse_load_test.erl index 3219314..5ff308e 100644 --- a/test/ibrowse_load_test.erl +++ b/test/ibrowse_load_test.erl @@ -1,77 +1,181 @@ -module(ibrowse_load_test). --export([go/3]). +-compile(export_all). --define(counters, ibrowse_load_test_counters). +-define(ibrowse_load_test_counters, ibrowse_load_test_counters). -go(URL, N_workers, N_reqs) -> - spawn(fun() -> - go_1(URL, N_workers, N_reqs) - end). +start(Num_workers, Num_requests, Max_sess) -> + proc_lib:spawn(fun() -> + start_1(Num_workers, Num_requests, Max_sess) + end). -go_1(URL, N_workers, N_reqs) -> - ets:new(?counters, [named_table, public]), +query_state() -> + ibrowse_load_test ! query_state. + +shutdown() -> + ibrowse_load_test ! shutdown. + +start_1(Num_workers, Num_requests, Max_sess) -> + register(ibrowse_load_test, self()), + application:start(ibrowse), + application:set_env(ibrowse, inactivity_timeout, 5000), + Ulimit = os:cmd("ulimit -n"), + case catch list_to_integer(string:strip(Ulimit, right, $\n)) of + X when is_integer(X), X > 3000 -> + ok; + X -> + io:format("Load test not starting. {insufficient_value_for_ulimit, ~p}~n", [X]), + exit({insufficient_value_for_ulimit, X}) + end, + ets:new(?ibrowse_load_test_counters, [named_table, public]), + ets:new(ibrowse_load_timings, [named_table, public]), try - ets:insert(?counters, [{success, 0}, - {failed, 0}, - {timeout, 0}, - {retry_later, 0}]), - Start_time = now(), - Pids = spawn_workers(N_workers, N_reqs, URL, self(), []), - wait_for_pids(Pids), - End_time = now(), - Time_taken = trunc(round(timer:now_diff(End_time, Start_time) / 1000000)), - [{_, Success_reqs}] = ets:lookup(?counters, success), - Total_reqs = N_workers*N_reqs, - Req_rate = case Time_taken > 0 of - true -> - trunc(Success_reqs / Time_taken); - false when Success_reqs == Total_reqs -> - withabix; - false -> - without_a_bix - end, - io:format("Stats : ~p~n", [ets:tab2list(?counters)]), - io:format("Total reqs : ~p~n", [Total_reqs]), - io:format("Time taken : ~p seconds~n", [Time_taken]), - io:format("Reqs / sec : ~p~n", [Req_rate]) - catch Class:Reason -> - io:format("Load test crashed. Reason: ~p~n" - "Stacktrace : ~p~n", - [{Class, Reason}, erlang:get_stacktrace()]) + ets:insert(?ibrowse_load_test_counters, [{success, 0}, + {failed, 0}, + {timeout, 0}, + {retry_later, 0}, + {one_request_only, 0} + ]), + ibrowse:set_max_sessions("localhost", 8081, Max_sess), + Start_time = now(), + Workers = spawn_workers(Num_workers, Num_requests), + erlang:send_after(1000, self(), print_diagnostics), + ok = wait_for_workers(Workers), + End_time = now(), + Time_in_secs = trunc(round(timer:now_diff(End_time, Start_time) / 1000000)), + Req_count = Num_workers * Num_requests, + [{_, Success_count}] = ets:lookup(?ibrowse_load_test_counters, success), + case Success_count == Req_count of + true -> + io:format("Test success. All requests succeeded~n", []); + false when Success_count > 0 -> + io:format("Test failed. Some successes~n", []); + false -> + io:format("Test failed. ALL requests FAILED~n", []) + end, + case Time_in_secs > 0 of + true -> + io:format("Reqs/sec achieved : ~p~n", [trunc(round(Success_count / Time_in_secs))]); + false -> + ok + end, + io:format("Load test results:~n~p~n", [ets:tab2list(?ibrowse_load_test_counters)]), + io:format("Timings: ~p~n", [calculate_timings()]) + catch Err -> + io:format("Err: ~p~n", [Err]) after - ets:delete(?counters) + ets:delete(?ibrowse_load_test_counters), + ets:delete(ibrowse_load_timings), + unregister(ibrowse_load_test) end. -spawn_workers(0, _, _, _, Acc) -> - Acc; -spawn_workers(N_workers, N_reqs, URL, Parent, Acc) -> - Pid = spawn(fun() -> - worker(N_reqs, URL, Parent) - end), - spawn_workers(N_workers - 1, N_reqs, URL, Parent, [Pid | Acc]). +calculate_timings() -> + {Max, Min, Mean} = get_mmv(ets:first(ibrowse_load_timings), {0, 9999999, 0}), + Variance = trunc(round(ets:foldl(fun({_, X}, X_acc) -> + (X - Mean)*(X-Mean) + X_acc + end, 0, ibrowse_load_timings) / ets:info(ibrowse_load_timings, size))), + Std_dev = trunc(round(math:sqrt(Variance))), + {ok, [{max, Max}, + {min, Min}, + {mean, Mean}, + {variance, Variance}, + {standard_deviation, Std_dev}]}. + +get_mmv('$end_of_table', {Max, Min, Total}) -> + Mean = trunc(round(Total / ets:info(ibrowse_load_timings, size))), + {Max, Min, Mean}; +get_mmv(Key, {Max, Min, Total}) -> + [{_, V}] = ets:lookup(ibrowse_load_timings, Key), + get_mmv(ets:next(ibrowse_load_timings, Key), {max(Max, V), min(Min, V), Total + V}). + + +spawn_workers(Num_w, Num_r) -> + spawn_workers(Num_w, Num_r, self(), []). -wait_for_pids([Pid | T]) -> +spawn_workers(0, _Num_requests, _Parent, Acc) -> + lists:reverse(Acc); +spawn_workers(Num_workers, Num_requests, Parent, Acc) -> + Pid_ref = spawn_monitor(fun() -> + random:seed(now()), + case catch worker_loop(Parent, Num_requests) of + {'EXIT', Rsn} -> + io:format("Worker crashed with reason: ~p~n", [Rsn]); + _ -> + ok + end + end), + spawn_workers(Num_workers - 1, Num_requests, Parent, [Pid_ref | Acc]). + +wait_for_workers([]) -> + ok; +wait_for_workers([{Pid, Pid_ref} | T] = Pids) -> receive {done, Pid} -> - wait_for_pids(T); + wait_for_workers(T); {done, Some_pid} -> - wait_for_pids([Pid | (T -- [Some_pid])]) - end; -wait_for_pids([]) -> - ok. - + wait_for_workers([{Pid, Pid_ref} | lists:keydelete(Some_pid, 1, T)]); + print_diagnostics -> + io:format("~1000.p~n", [ibrowse:get_metrics()]), + erlang:send_after(1000, self(), print_diagnostics), + wait_for_workers(Pids); + query_state -> + io:format("Waiting for ~p~n", [Pids]), + wait_for_workers(Pids); + shutdown -> + io:format("Shutting down on command. Still waiting for ~p workers~n", [length(Pids)]); + {'DOWN', _, process, _, normal} -> + wait_for_workers(Pids); + {'DOWN', _, process, Down_pid, Rsn} -> + io:format("Worker ~p died. Reason: ~p~n", [Down_pid, Rsn]), + wait_for_workers(lists:keydelete(Down_pid, 1, Pids)); + X -> + io:format("Recvd unknown msg: ~p~n", [X]), + wait_for_workers(Pids) + end. -worker(0, _, Parent) -> +worker_loop(Parent, 0) -> Parent ! {done, self()}; -worker(N, URL, Parent) -> - case ibrowse:send_req(URL, [], get) of +worker_loop(Parent, N) -> + Delay = random:uniform(100), + Url = case Delay rem 10 of + %% Change 10 to some number between 0-9 depending on how + %% much chaos you want to introduce into the server + %% side. The higher the number, the more often the + %% server will close a connection after serving the + %% first request, thereby forcing the client to + %% retry. Any number of 10 or higher will disable this + %% chaos mechanism + 10 -> + ets:update_counter(?ibrowse_load_test_counters, one_request_only, 1), + "http://localhost:8081/ibrowse_handle_one_request_only"; + _ -> + "http://localhost:8081/blah" + end, + Start_time = now(), + Res = ibrowse:send_req(Url, [], get), + End_time = now(), + Time_taken = trunc(round(timer:now_diff(End_time, Start_time) / 1000)), + ets:insert(ibrowse_load_timings, {now(), Time_taken}), + case Res of {ok, "200", _, _} -> - ets:update_counter(?counters, success, 1); + ets:update_counter(?ibrowse_load_test_counters, success, 1); {error, req_timedout} -> - ets:update_counter(?counters, timeout, 1); + ets:update_counter(?ibrowse_load_test_counters, timeout, 1); {error, retry_later} -> - ets:update_counter(?counters, retry_later, 1); + ets:update_counter(?ibrowse_load_test_counters, retry_later, 1); + {error, Reason} -> + update_unknown_counter(Reason, 1); _ -> - ets:update_counter(?counters, failed, 1) + io:format("~p -- Res: ~p~n", [self(), Res]), + ets:update_counter(?ibrowse_load_test_counters, failed, 1) end, - worker(N - 1, URL, Parent). + timer:sleep(Delay), + worker_loop(Parent, N - 1). + +update_unknown_counter(Counter, Inc_val) -> + case catch ets:update_counter(?ibrowse_load_test_counters, Counter, Inc_val) of + {'EXIT', _} -> + ets:insert_new(?ibrowse_load_test_counters, {Counter, 0}), + update_unknown_counter(Counter, Inc_val); + _ -> + ok + end. diff --git a/test/ibrowse_test.erl b/test/ibrowse_test.erl index 407ffcb..e216e82 100644 --- a/test/ibrowse_test.erl +++ b/test/ibrowse_test.erl @@ -8,9 +8,10 @@ load_test/3, send_reqs_1/3, do_send_req/2, + local_unit_tests/0, unit_tests/0, - unit_tests/1, - unit_tests_1/2, + unit_tests/2, + unit_tests_1/3, ue_test/0, ue_test/1, verify_chunked_streaming/0, @@ -34,9 +35,13 @@ test_303_response_with_a_body/1, test_binary_headers/0, test_binary_headers/1, - test_generate_body_0/0 + test_generate_body_0/0, + test_retry_of_requests/0, + test_retry_of_requests/1 ]). +-include("ibrowse.hrl"). + test_stream_once(Url, Method, Options) -> test_stream_once(Url, Method, Options, 5000). @@ -90,6 +95,8 @@ send_reqs_1(Url, NumWorkers, NumReqsPerWorker) -> ets:new(pid_table, [named_table, public]), ets:new(ibrowse_test_results, [named_table, public]), ets:new(ibrowse_errors, [named_table, public, ordered_set]), + ets:new(ibrowse_counter, [named_table, public, ordered_set]), + ets:insert(ibrowse_counter, {req_id, 1}), init_results(), process_flag(trap_exit, true), log_msg("Starting spawning of workers...~n", []), @@ -207,6 +214,16 @@ dump_errors(Key, Iod) -> %%------------------------------------------------------------------------------ %% Unit Tests %%------------------------------------------------------------------------------ +-define(LOCAL_TESTS, [ + {local_test_fun, test_20122010, []}, + {local_test_fun, test_pipeline_head_timeout, []}, + {local_test_fun, test_head_transfer_encoding, []}, + {local_test_fun, test_head_response_with_body, []}, + {local_test_fun, test_303_response_with_a_body, []}, + {local_test_fun, test_binary_headers, []}, + {local_test_fun, test_retry_of_requests, []} + ]). + -define(TEST_LIST, [{"http://intranet/messenger", get}, {"http://www.google.co.uk", get}, {"http://www.google.com", get}, @@ -236,27 +253,26 @@ dump_errors(Key, Iod) -> {"http://jigsaw.w3.org/HTTP/Basic/", get, [{basic_auth, {"guest", "guest"}}]}, {"http://jigsaw.w3.org/HTTP/CL/", get}, {"http://www.httpwatch.com/httpgallery/chunked/", get}, - {"https://github.com", get, [{ssl_options, [{depth, 2}]}]}, - {local_test_fun, test_20122010, []}, - {local_test_fun, test_pipeline_head_timeout, []}, - {local_test_fun, test_head_transfer_encoding, []}, - {local_test_fun, test_head_response_with_body, []}, - {local_test_fun, test_303_response_with_a_body, []}, - {local_test_fun, test_binary_headers, []} - ]). + {"https://github.com", get, [{ssl_options, [{depth, 2}]}]} + ] ++ ?LOCAL_TESTS). + +local_unit_tests() -> + error_logger:tty(false), + unit_tests([], ?LOCAL_TESTS), + error_logger:tty(true). unit_tests() -> - unit_tests([]). + unit_tests([], ?TEST_LIST). -unit_tests(Options) -> +unit_tests(Options, Test_list) -> application:start(crypto), application:start(public_key), application:start(ssl), (catch ibrowse_test_server:start_server(8181, tcp)), - ibrowse:start(), + application:start(ibrowse), Options_1 = Options ++ [{connect_timeout, 5000}], Test_timeout = proplists:get_value(test_timeout, Options, 60000), - {Pid, Ref} = erlang:spawn_monitor(?MODULE, unit_tests_1, [self(), Options_1]), + {Pid, Ref} = erlang:spawn_monitor(?MODULE, unit_tests_1, [self(), Options_1, Test_list]), receive {done, Pid} -> ok; @@ -269,14 +285,14 @@ unit_tests(Options) -> catch ibrowse_test_server:stop_server(8181), ok. -unit_tests_1(Parent, Options) -> +unit_tests_1(Parent, Options, Test_list) -> lists:foreach(fun({local_test_fun, Fun_name, Args}) -> execute_req(local_test_fun, Fun_name, Args); ({Url, Method}) -> execute_req(Url, Method, Options); ({Url, Method, X_Opts}) -> execute_req(Url, Method, X_Opts ++ Options) - end, ?TEST_LIST), + end, Test_list), Parent ! {done, self()}. verify_chunked_streaming() -> @@ -425,6 +441,7 @@ maybe_stream_next(Req_id, Options) -> end. execute_req(local_test_fun, Method, Args) -> + reset_ibrowse(), io:format(" ~-54.54w: ", [Method]), Result = (catch apply(?MODULE, Method, Args)), io:format("~p~n", [Result]); @@ -538,6 +555,74 @@ test_303_response_with_a_body(Url) -> {test_failed, Res} end. +%%------------------------------------------------------------------------------ +%% Test that retry of requests happens correctly, and that ibrowse doesn't retry +%% if there is not enough time left +%%------------------------------------------------------------------------------ +test_retry_of_requests() -> + clear_msg_q(), + test_retry_of_requests("http://localhost:8181/ibrowse_handle_one_request_only_with_delay"). + +test_retry_of_requests(Url) -> + Timeout_1 = 2050, + Res_1 = test_retry_of_requests(Url, Timeout_1), + case lists:filter(fun({_Pid, {ok, "200", _, _}}) -> + true; + (_) -> false + end, Res_1) of + [_|_] = X -> + Res_1_1 = Res_1 -- X, + case lists:all( + fun({_Pid, {error, retry_later}}) -> + true; + (_) -> + false + end, Res_1_1) of + true -> + ok; + false -> + exit({failed, Timeout_1, Res_1}) + end; + _ -> + exit({failed, Timeout_1, Res_1}) + end, + reset_ibrowse(), + Timeout_2 = 2200, + Res_2 = test_retry_of_requests(Url, Timeout_2), + case lists:filter(fun({_Pid, {ok, "200", _, _}}) -> + true; + (_) -> false + end, Res_2) of + [_|_] = Res_2_X -> + Res_2_1 = Res_2 -- Res_2_X, + case lists:all( + fun({_Pid, {error, X_err_2}}) -> + (X_err_2 == retry_later) orelse (X_err_2 == req_timedout); + (_) -> + false + end, Res_2_1) of + true -> + ok; + false -> + exit({failed, Timeout_2, Res_2}) + end; + _ -> + exit({failed, Timeout_2, Res_2}) + end, + success. + +test_retry_of_requests(Url, Timeout) -> + #url{host = Host, port = Port} = ibrowse_lib:parse_url(Url), + ibrowse:set_max_sessions(Host, Port, 1), + Parent = self(), + Pids = lists:map(fun(_) -> + spawn(fun() -> + Res = (catch ibrowse:send_req(Url, [], get, [], [], Timeout)), + Parent ! {self(), Res} + end) + end, lists:seq(1,10)), + accumulate_worker_resp(Pids). + %%------------------------------------------------------------------------------ %% Test what happens when the request at the head of a pipeline times out %%------------------------------------------------------------------------------ @@ -547,22 +632,27 @@ test_pipeline_head_timeout() -> test_pipeline_head_timeout(Url) -> {ok, Pid} = ibrowse:spawn_worker_process(Url), + Fixed_timeout = 2000, Test_parent = self(), Fun = fun({fixed, Timeout}) -> - spawn(fun() -> - do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout) - end); - (Timeout_mult) -> - spawn(fun() -> - Timeout = 1000 + Timeout_mult*1000, - do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout) - end) - end, - Pids = [Fun(X) || X <- [{fixed, 32000} | lists:seq(1,10)]], + X_pid = spawn(fun() -> + do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout) + end), + %% io:format("Pid ~p with a fixed timeout~n", [X_pid]), + X_pid; + (Timeout_mult) -> + Timeout = Fixed_timeout + Timeout_mult*1000, + X_pid = spawn(fun() -> + do_test_pipeline_head_timeout(Url, Pid, Test_parent, Timeout) + end), + %% io:format("Pid ~p with a timeout of ~p~n", [X_pid, Timeout]), + X_pid + end, + Pids = [Fun(X) || X <- [{fixed, Fixed_timeout} | lists:seq(1,10)]], Result = accumulate_worker_resp(Pids), case lists:all(fun({_, X_res}) -> - X_res == {error,req_timedout} - end, Result) of + (X_res == {error,req_timedout}) orelse (X_res == {error, connection_closed}) + end, Result) of true -> success; false -> @@ -725,3 +815,7 @@ do_trace(true, Fmt, Args) -> io:format("~s -- " ++ Fmt, [ibrowse_lib:printable_date() | Args]); do_trace(_, _, _) -> ok. + +reset_ibrowse() -> + application:stop(ibrowse), + application:start(ibrowse). diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index 703227d..1d72210 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -15,25 +15,29 @@ start_server(Port, Sock_type) -> Fun = fun() -> - Name = server_proc_name(Port), - register(Name, self()), - case do_listen(Sock_type, Port, [{active, false}, - {reuseaddr, true}, - {nodelay, true}, - {packet, http}]) of - {ok, Sock} -> - do_trace("Server listening on port: ~p~n", [Port]), - accept_loop(Sock, Sock_type); - Err -> - erlang:error( + Proc_name = server_proc_name(Port), + case whereis(Proc_name) of + undefined -> + register(Proc_name, self()), + case do_listen(Sock_type, Port, [{active, false}, + {reuseaddr, true}, + {nodelay, true}, + {packet, http}]) of + {ok, Sock} -> + do_trace("Server listening on port: ~p~n", [Port]), + accept_loop(Sock, Sock_type); + Err -> + erlang:error( lists:flatten( - io_lib:format( - "Failed to start server on port ~p. ~p~n", - [Port, Err]))), - exit({listen_error, Err}) - end, - unregister(Name) - end, + io_lib:format( + "Failed to start server on port ~p. ~p~n", + [Port, Err]))), + exit({listen_error, Err}) + end; + _X -> + ok + end + end, spawn_link(Fun). stop_server(Port) -> @@ -88,12 +92,16 @@ server_loop(Sock, Sock_type, #request{headers = Headers} = Req) -> {http, Sock, {http_header, _, _, _, _} = H} -> server_loop(Sock, Sock_type, Req#request{headers = [H | Headers]}); {http, Sock, http_eoh} -> - process_request(Sock, Sock_type, Req), - server_loop(Sock, Sock_type, #request{}); + case process_request(Sock, Sock_type, Req) of + close_connection -> + gen_tcp:shutdown(Sock, read_write); + _ -> + server_loop(Sock, Sock_type, #request{}) + end; {http, Sock, {http_error, Err}} -> - do_trace("Error parsing HTTP request:~n" - "Req so far : ~p~n" - "Err : ", [Req, Err]), + io:format("Error parsing HTTP request:~n" + "Req so far : ~p~n" + "Err : ~p", [Req, Err]), exit({http_error, Err}); {setopts, Opts} -> setopts(Sock, Sock_type, Opts), @@ -104,9 +112,9 @@ server_loop(Sock, Sock_type, #request{headers = Headers} = Req) -> stop -> ok; Other -> - do_trace("Recvd unknown msg: ~p~n", [Other]), + io:format("Recvd unknown msg: ~p~n", [Other]), exit({unknown_msg, Other}) - after 5000 -> + after 120000 -> do_trace("Timing out client connection~n", []), ok end. @@ -145,7 +153,7 @@ process_request(Sock, Sock_type, headers = _Headers, uri = {abs_path, "/ibrowse_inac_timeout_test"}} = Req) -> do_trace("Recvd req: ~p. Sleeping for 30 secs...~n", [Req]), - timer:sleep(30000), + timer:sleep(3000), do_trace("...Sending response now.~n", []), Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>, do_send(Sock, Sock_type, Resp); @@ -178,7 +186,7 @@ process_request(Sock, Sock_type, #request{method='HEAD', headers = _Headers, uri = {abs_path, "/ibrowse_head_test"}}) -> - Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\nTransfer-Encoding: chunked\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>, + Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\Date: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>, do_send(Sock, Sock_type, Resp); process_request(Sock, Sock_type, #request{method='POST', @@ -192,10 +200,26 @@ process_request(Sock, Sock_type, uri = {abs_path, "/ibrowse_303_with_body_test"}}) -> Resp = <<"HTTP/1.1 303 See Other\r\nLocation: http://example.org\r\nContent-Length: 5\r\n\r\nabcde">>, do_send(Sock, Sock_type, Resp); +process_request(Sock, Sock_type, + #request{method='GET', + headers = _Headers, + uri = {abs_path, "/ibrowse_handle_one_request_only_with_delay"}}) -> + timer:sleep(2000), + Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>, + do_send(Sock, Sock_type, Resp), + close_connection; +process_request(Sock, Sock_type, + #request{method='GET', + headers = _Headers, + uri = {abs_path, "/ibrowse_handle_one_request_only"}}) -> + Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>, + do_send(Sock, Sock_type, Resp), + close_connection; process_request(Sock, Sock_type, Req) -> do_trace("Recvd req: ~p~n", [Req]), Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>, - do_send(Sock, Sock_type, Resp). + do_send(Sock, Sock_type, Resp), + timer:sleep(random:uniform(100)). do_send(Sock, tcp, Resp) -> gen_tcp:send(Sock, Resp); From afccba0bfae36df0748145db34956fab72159234 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Wed, 13 Aug 2014 11:10:15 +0100 Subject: [PATCH 11/45] Cleanup correctly on shutdown --- src/ibrowse_lb.erl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index a802f87..88b169b 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -112,10 +112,7 @@ handle_call(stop, _From, #state{ets_tid = undefined} = State) -> {stop, normal, State}; handle_call(stop, _From, #state{ets_tid = Tid} = State) -> - ets:foldl(fun({Pid, _, _}, Acc) -> - ibrowse_http_client:stop(Pid), - Acc - end, [], Tid), + stop_all_conn_procs(Tid), gen_server:reply(_From, ok), {stop, normal, State}; @@ -196,10 +193,17 @@ handle_info(_Info, State) -> %% Description: Shutdown the server %% Returns: any (ignored by gen_server) %%-------------------------------------------------------------------- -terminate(_Reason, #state{host = Host, port = Port} = _State) -> +terminate(_Reason, #state{host = Host, port = Port, ets_tid = Tid} = _State) -> catch ets:delete(ibrowse_lb, {Host, Port}), + stop_all_conn_procs(Tid), ok. +stop_all_conn_procs(Tid) -> + ets:foldl(fun({{_, _, Pid}, _}, Acc) -> + ibrowse_http_client:stop(Pid), + Acc + end, [], Tid). + %%-------------------------------------------------------------------- %% Func: code_change/3 %% Purpose: Convert process state when code is changed From a51834ff4bf7b74fd11201d9c5e24e8d99469c5e Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Wed, 13 Aug 2014 11:18:01 +0100 Subject: [PATCH 12/45] Tweak get_metrics --- src/ibrowse.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ibrowse.erl b/src/ibrowse.erl index 6c87364..fbb4b83 100644 --- a/src/ibrowse.erl +++ b/src/ibrowse.erl @@ -717,7 +717,7 @@ get_metrics(Host, Port) -> Size = ets:info(Tid, size), case Size of 0 -> - {Lb_pid, MsgQueueSize, undefined, 0, {{0, 0}, {0, 0}}}; + {Lb_pid, MsgQueueSize, Tid, 0, {{0, 0}, {0, 0}}}; _ -> {First_p_sz, _, _} = ets:first(Tid), {Last_p_sz, _, _} = ets:last(Tid), From d61dd9a074ac7f9d63445ac9a19670a11aaf9640 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Wed, 13 Aug 2014 11:22:55 +0100 Subject: [PATCH 13/45] Changed travis-ci build status link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b68f197..5820447 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ibrowse [![Build Status](https://secure.travis-ci.org/johannesh/ibrowse.png)](http://travis-ci.org/johannesh/ibrowse) +# ibrowse [![Build Status](https://secure.travis-ci.org/cmullaparthi/ibrowse.png)](http://travis-ci.org/cmullaparthi/ibrowse) ibrowse is a HTTP client written in erlang. From d6e542b25676bd33a77e6ae54fb3b73aeec89978 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Tue, 18 Nov 2014 19:55:27 +0000 Subject: [PATCH 14/45] Fixed test server stop message handling Moved receive of stop message to listening process instead of connection and modified listener to come up for air every once in a while to process. --- test/ibrowse_test_server.erl | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index 703227d..a00fe5b 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -1,5 +1,7 @@ %%% File : ibrowse_test_server.erl %%% Author : Chandrashekhar Mullaparthi +%%% Benjamin Lee +%%% Brian Richards %%% Description : A server to simulate various test scenarios %%% Created : 17 Oct 2010 by Chandrashekhar Mullaparthi @@ -12,6 +14,7 @@ -record(request, {method, uri, version, headers = [], body = []}). -define(dec2hex(X), erlang:integer_to_list(X, 16)). +-define(ACCEPT_TIMEOUT_MS, 1000). start_server(Port, Sock_type) -> Fun = fun() -> @@ -38,6 +41,7 @@ start_server(Port, Sock_type) -> stop_server(Port) -> server_proc_name(Port) ! stop, + timer:sleep(2000), % wait for server to receive msg and unregister ok. server_proc_name(Port) -> @@ -51,9 +55,9 @@ do_listen(ssl, Port, Opts) -> ssl:listen(Port, Opts). do_accept(tcp, Listen_sock) -> - gen_tcp:accept(Listen_sock); + gen_tcp:accept(Listen_sock, ?ACCEPT_TIMEOUT_MS); do_accept(ssl, Listen_sock) -> - ssl:ssl_accept(Listen_sock). + ssl:ssl_accept(Listen_sock, ?ACCEPT_TIMEOUT_MS). accept_loop(Sock, Sock_type) -> case do_accept(Sock_type, Sock) of @@ -65,6 +69,13 @@ accept_loop(Sock, Sock_type) -> set_controlling_process(Conn, Sock_type, Pid), Pid ! {setopts, [{active, true}]}, accept_loop(Sock, Sock_type); + {error, timeout} -> + receive + stop -> + ok + after 10 -> + accept_loop(Sock, Sock_type) + end; Err -> Err end. @@ -101,8 +112,6 @@ server_loop(Sock, Sock_type, #request{headers = Headers} = Req) -> {tcp_closed, Sock} -> do_trace("Client closed connection~n", []), ok; - stop -> - ok; Other -> do_trace("Recvd unknown msg: ~p~n", [Other]), exit({unknown_msg, Other}) From 8d2576178742da2d90832080e6bf3f6997852f15 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Tue, 18 Nov 2014 19:57:05 +0000 Subject: [PATCH 15/45] Ignored temporary .rebar files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 12f55c8..1491146 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ doc/edoc-info Emakefile *.bat .dialyzer_plt +.rebar From 0ce1b45c379fcc1ee1328fd0c01eb8b961b2eb80 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Tue, 18 Nov 2014 19:57:31 +0000 Subject: [PATCH 16/45] Created base for functional tests New eunit based test harness for functional tests that require a running test server in order to run. --- test/ibrowse_functional_tests.erl | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/ibrowse_functional_tests.erl diff --git a/test/ibrowse_functional_tests.erl b/test/ibrowse_functional_tests.erl new file mode 100644 index 0000000..a291eb5 --- /dev/null +++ b/test/ibrowse_functional_tests.erl @@ -0,0 +1,39 @@ +%%% File : ibrowse_functional_tests.erl +%%% Authors : Benjamin Lee +%%% Brian Richards +%%% Description : Functional tests of the ibrowse library using a live test HTTP server +%%% Created : 18 November 2014 by Benjamin Lee + +-module(ibrowse_functional_tests). + +-include_lib("eunit/include/eunit.hrl"). +-define(PER_TEST_TIMEOUT_SEC, 60). +-define(TIMEDTEST(Desc, Fun), {Desc, {timeout, ?PER_TEST_TIMEOUT_SEC, fun Fun/0}}). + +-define(SERVER_PORT, 8181). +-define(BASE_URL, "http://localhost:" ++ integer_to_list(?SERVER_PORT)). + +setup() -> + application:start(crypto), + application:start(public_key), + application:start(ssl), + ibrowse_test_server:start_server(?SERVER_PORT, tcp), + ibrowse:start(), + ok. + +teardown(_) -> + ibrowse:stop(), + ibrowse_test_server:stop_server(?SERVER_PORT), + ok. + +running_server_fixture_test_() -> + {foreach, + fun setup/0, + fun teardown/1, + [ + ?TIMEDTEST("Simple request can be honored", simple_request) + ] + }. + +simple_request() -> + ?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [])). From 6bf1951c99a97adb394e0bacf6af4a2221652759 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Tue, 18 Nov 2014 20:21:53 +0000 Subject: [PATCH 17/45] Add new endpoint to test_server for slow responses New endpoint will never send back a response and can be used for verifying timeouts and long running processes. --- test/ibrowse_functional_tests.erl | 6 +++++- test/ibrowse_test_server.erl | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/ibrowse_functional_tests.erl b/test/ibrowse_functional_tests.erl index a291eb5..c2bae86 100644 --- a/test/ibrowse_functional_tests.erl +++ b/test/ibrowse_functional_tests.erl @@ -31,9 +31,13 @@ running_server_fixture_test_() -> fun setup/0, fun teardown/1, [ - ?TIMEDTEST("Simple request can be honored", simple_request) + ?TIMEDTEST("Simple request can be honored", simple_request), + ?TIMEDTEST("Slow server causes timeout", slow_server_timeout) ] }. simple_request() -> ?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [])). + +slow_server_timeout() -> + ?assertMatch({error, req_timedout}, ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [], 5000)). diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index a00fe5b..c102431 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -201,6 +201,8 @@ process_request(Sock, Sock_type, uri = {abs_path, "/ibrowse_303_with_body_test"}}) -> Resp = <<"HTTP/1.1 303 See Other\r\nLocation: http://example.org\r\nContent-Length: 5\r\n\r\nabcde">>, do_send(Sock, Sock_type, Resp); +process_request(_Sock, _Sock_type, #request{uri = {abs_path, "/never_respond"} } ) -> + noop; process_request(Sock, Sock_type, Req) -> do_trace("Recvd req: ~p~n", [Req]), Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>, From 1a5c80f3d0bfbceb1c32b8fd9832dcdbfa783098 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Tue, 18 Nov 2014 22:03:06 +0000 Subject: [PATCH 18/45] Created broken test demonstrating imbalanced load In preparation for work to ensure that request load is balanced across connections, created failing functional test to demonstrate current behavior. --- test/ibrowse_functional_tests.erl | 32 ++++++++++++++++++++++++++++++- test/ibrowse_test_server.erl | 10 +++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/test/ibrowse_functional_tests.erl b/test/ibrowse_functional_tests.erl index c2bae86..0a68e14 100644 --- a/test/ibrowse_functional_tests.erl +++ b/test/ibrowse_functional_tests.erl @@ -32,7 +32,8 @@ running_server_fixture_test_() -> fun teardown/1, [ ?TIMEDTEST("Simple request can be honored", simple_request), - ?TIMEDTEST("Slow server causes timeout", slow_server_timeout) + ?TIMEDTEST("Slow server causes timeout", slow_server_timeout), + ?TIMEDTEST("Requests are balanced over connections", balanced_connections) ] }. @@ -41,3 +42,32 @@ simple_request() -> slow_server_timeout() -> ?assertMatch({error, req_timedout}, ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [], 5000)). + +balanced_connections() -> + MaxSessions = 4, + MaxPipeline = 100, + RequestsSent = 80, + BalancedNumberOfRequestsPerConnection = 20, + + ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()), + + Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], 30000) end, + times(RequestsSent, fun() -> spawn_link(Fun) end), + + timer:sleep(1000), + + Diffs = [Count - BalancedNumberOfRequestsPerConnection || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()], + ?assertEqual(MaxSessions, length(Diffs)), + + lists:foreach(fun(X) -> ?assertEqual(yep, close_to_zero(X)) end, Diffs). + +close_to_zero(0) -> yep; +close_to_zero(-1) -> yep; +close_to_zero(1) -> yep; +close_to_zero(X) -> {nope, X}. + +times(0, _) -> + ok; +times(X, Fun) -> + Fun(), + times(X - 1, Fun). diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index c102431..940552a 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -8,18 +8,21 @@ -module(ibrowse_test_server). -export([ start_server/2, - stop_server/1 + stop_server/1, + get_conn_pipeline_depth/0 ]). -record(request, {method, uri, version, headers = [], body = []}). -define(dec2hex(X), erlang:integer_to_list(X, 16)). -define(ACCEPT_TIMEOUT_MS, 1000). +-define(CONN_PIPELINE_DEPTH, conn_pipeline_depth). start_server(Port, Sock_type) -> Fun = fun() -> Name = server_proc_name(Port), register(Name, self()), + ets:new(?CONN_PIPELINE_DEPTH, [named_table, public, set]), case do_listen(Sock_type, Port, [{active, false}, {reuseaddr, true}, {nodelay, true}, @@ -44,6 +47,9 @@ stop_server(Port) -> timer:sleep(2000), % wait for server to receive msg and unregister ok. +get_conn_pipeline_depth() -> + ets:tab2list(?CONN_PIPELINE_DEPTH). + server_proc_name(Port) -> list_to_atom("ibrowse_test_server_"++integer_to_list(Port)). @@ -66,6 +72,7 @@ accept_loop(Sock, Sock_type) -> fun() -> server_loop(Conn, Sock_type, #request{}) end), + ets:insert(?CONN_PIPELINE_DEPTH, {Pid, 0}), set_controlling_process(Conn, Sock_type, Pid), Pid ! {setopts, [{active, true}]}, accept_loop(Sock, Sock_type); @@ -99,6 +106,7 @@ server_loop(Sock, Sock_type, #request{headers = Headers} = Req) -> {http, Sock, {http_header, _, _, _, _} = H} -> server_loop(Sock, Sock_type, Req#request{headers = [H | Headers]}); {http, Sock, http_eoh} -> + ets:update_counter(?CONN_PIPELINE_DEPTH, self(), 1), process_request(Sock, Sock_type, Req), server_loop(Sock, Sock_type, #request{}); {http, Sock, {http_error, Err}} -> From 39528b555d2764842b1ba602a075295d61842489 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Tue, 18 Nov 2014 22:24:55 +0000 Subject: [PATCH 19/45] Made ets table names clearer by usage Reuse of ibrowse_lb for named table holding load balancers and for name of unnamed table holding connections was confusing. --- include/ibrowse.hrl | 3 +++ src/ibrowse.erl | 22 +++++++++++----------- src/ibrowse_lb.erl | 12 ++++++------ 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/include/ibrowse.hrl b/include/ibrowse.hrl index 18dde82..76ab236 100644 --- a/include/ibrowse.hrl +++ b/include/ibrowse.hrl @@ -18,4 +18,7 @@ -record(ibrowse_conf, {key, value}). +-define(CONNECTIONS_LOCAL_TABLE, ibrowse_lb). +-define(LOAD_BALANCER_NAMED_TABLE, ibrowse_lb). + -endif. diff --git a/src/ibrowse.erl b/src/ibrowse.erl index 85bb75c..b541398 100644 --- a/src/ibrowse.erl +++ b/src/ibrowse.erl @@ -317,7 +317,7 @@ send_req(Url, Headers, Method, Body, Options, Timeout) -> #url{host = Host, port = Port, protocol = Protocol} = Parsed_url -> - Lb_pid = case ets:lookup(ibrowse_lb, {Host, Port}) of + Lb_pid = case ets:lookup(?LOAD_BALANCER_NAMED_TABLE, {Host, Port}) of [] -> get_lb_pid(Parsed_url); [#lb_pid{pid = Lb_pid_1}] -> @@ -659,7 +659,7 @@ get_metrics() -> true; (_) -> false - end, ets:tab2list(ibrowse_lb)), + end, ets:tab2list(?LOAD_BALANCER_NAMED_TABLE)), All_ets = ets:all(), lists:map(fun({lb_pid, {Host, Port}, Lb_pid}) -> case lists:dropwhile( @@ -678,7 +678,7 @@ get_metrics() -> end, Dests). get_metrics(Host, Port) -> - case ets:lookup(ibrowse_lb, {Host, Port}) of + case ets:lookup(?LOAD_BALANCER_NAMED_TABLE, {Host, Port}) of [] -> no_active_processes; [#lb_pid{pid = Lb_pid}] -> @@ -746,9 +746,9 @@ init(_) -> State = #state{}, put(my_trace_flag, State#state.trace), put(ibrowse_trace_token, "ibrowse"), - ibrowse_lb = ets:new(ibrowse_lb, [named_table, public, {keypos, 2}]), - ibrowse_conf = ets:new(ibrowse_conf, [named_table, protected, {keypos, 2}]), - ibrowse_stream = ets:new(ibrowse_stream, [named_table, public]), + ?LOAD_BALANCER_NAMED_TABLE = ets:new(?LOAD_BALANCER_NAMED_TABLE, [named_table, public, {keypos, 2}]), + ibrowse_conf = ets:new(ibrowse_conf, [named_table, protected, {keypos, 2}]), + ibrowse_stream = ets:new(ibrowse_stream, [named_table, public]), import_config(), {ok, #state{}}. @@ -833,7 +833,7 @@ set_config_value(Key, Val) -> %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- handle_call({get_lb_pid, #url{host = Host, port = Port} = Url}, _From, State) -> - Pid = do_get_connection(Url, ets:lookup(ibrowse_lb, {Host, Port})), + Pid = do_get_connection(Url, ets:lookup(?LOAD_BALANCER_NAMED_TABLE, {Host, Port})), {reply, Pid, State}; handle_call(stop, _From, State) -> @@ -841,7 +841,7 @@ handle_call(stop, _From, State) -> ets:foldl(fun(#lb_pid{pid = Pid}, Acc) -> ibrowse_lb:stop(Pid), Acc - end, [], ibrowse_lb), + end, [], ?LOAD_BALANCER_NAMED_TABLE), {stop, normal, ok, State}; handle_call({set_config_value, Key, Val}, _From, State) -> @@ -899,7 +899,7 @@ handle_info(all_trace_off, State) -> (_, Acc) -> Acc end, - ets:foldl(Fun, undefined, ibrowse_lb), + ets:foldl(Fun, undefined, ?LOAD_BALANCER_NAMED_TABLE), ets:select_delete(ibrowse_conf, [{{ibrowse_conf,{trace,'$1','$2'},true},[],['true']}]), {noreply, State}; @@ -915,7 +915,7 @@ handle_info({trace, Bool, Host, Port}, State) -> (_, Acc) -> Acc end, - ets:foldl(Fun, undefined, ibrowse_lb), + ets:foldl(Fun, undefined, ?LOAD_BALANCER_NAMED_TABLE), ets:insert(ibrowse_conf, #ibrowse_conf{key = {trace, Host, Port}, value = Bool}), {noreply, State}; @@ -944,7 +944,7 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- do_get_connection(#url{host = Host, port = Port}, []) -> {ok, Pid} = ibrowse_lb:start_link([Host, Port]), - ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, pid = Pid}), + ets:insert(?LOAD_BALANCER_NAMED_TABLE, #lb_pid{host_port = {Host, Port}, pid = Pid}), Pid; do_get_connection(_Url, [#lb_pid{pid = Pid}]) -> Pid. diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index aa381a4..9f49e0d 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -70,7 +70,7 @@ init([Host, Port]) -> Max_pipe_sz = ibrowse:get_config_value({max_pipeline_size, Host, Port}, 10), put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)), put(ibrowse_trace_token, ["LB: ", Host, $:, integer_to_list(Port)]), - Tid = ets:new(ibrowse_lb, [public, ordered_set]), + Tid = ets:new(?CONNECTIONS_LOCAL_TABLE, [public, ordered_set]), {ok, #state{parent_pid = whereis(ibrowse), host = Host, port = Port, @@ -199,9 +199,9 @@ handle_info({trace, Bool}, #state{ets_tid = Tid} = State) -> handle_info(timeout, State) -> %% We can't shutdown the process immediately because a request %% might be in flight. So we first remove the entry from the - %% ibrowse_lb ets table, and then shutdown a couple of seconds - %% later - ets:delete(ibrowse_lb, {State#state.host, State#state.port}), + %% load balancer named ets table, and then shutdown a couple + %% of seconds later + ets:delete(?LOAD_BALANCER_NAMED_TABLE, {State#state.host, State#state.port}), erlang:send_after(2000, self(), shutdown), {noreply, State#state{proc_state = shutting_down}}; @@ -219,7 +219,7 @@ handle_info(_Info, State) -> terminate(_Reason, #state{host = Host, port = Port}) -> % Use delete_object instead of delete in case another process for this host/port % has been spawned, in which case will be deleting the wrong record because pid won't match. - ets:delete_object(ibrowse_lb, #lb_pid{host_port = {Host, Port}, pid = self()}), + ets:delete_object(?LOAD_BALANCER_NAMED_TABLE, #lb_pid{host_port = {Host, Port}, pid = self()}), ok. %%-------------------------------------------------------------------- @@ -257,7 +257,7 @@ find_best_connection(Pid, Tid, Max_pipe) -> end. maybe_create_ets(#state{ets_tid = undefined} = State) -> - Tid = ets:new(ibrowse_lb, [public, ordered_set]), + Tid = ets:new(?CONNECTIONS_LOCAL_TABLE, [public, ordered_set]), State#state{ets_tid = Tid}; maybe_create_ets(State) -> State. From 2cb114418758e338f4f384d8f645212c59ef4393 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Tue, 18 Nov 2014 22:32:02 +0000 Subject: [PATCH 20/45] Aliased remaining production ets table names For consistency, added macro aliases for remaining ets table names to be found all in one place. --- include/ibrowse.hrl | 2 ++ src/ibrowse.erl | 26 +++++++++++++------------- src/ibrowse_http_client.erl | 6 +++--- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/include/ibrowse.hrl b/include/ibrowse.hrl index 76ab236..d6b8f1c 100644 --- a/include/ibrowse.hrl +++ b/include/ibrowse.hrl @@ -20,5 +20,7 @@ -define(CONNECTIONS_LOCAL_TABLE, ibrowse_lb). -define(LOAD_BALANCER_NAMED_TABLE, ibrowse_lb). +-define(CONF_TABLE, ibrowse_conf). +-define(STREAM_TABLE, ibrowse_stream). -endif. diff --git a/src/ibrowse.erl b/src/ibrowse.erl index b541398..0ade277 100644 --- a/src/ibrowse.erl +++ b/src/ibrowse.erl @@ -558,7 +558,7 @@ send_req_direct(Conn_pid, Url, Headers, Method, Body, Options, Timeout) -> %% stream_to option %% @spec stream_next(Req_id :: req_id()) -> ok | {error, unknown_req_id} stream_next(Req_id) -> - case ets:lookup(ibrowse_stream, {req_id_pid, Req_id}) of + case ets:lookup(?STREAM_TABLE, {req_id_pid, Req_id}) of [] -> {error, unknown_req_id}; [{_, Pid}] -> @@ -573,7 +573,7 @@ stream_next(Req_id) -> %% error returned. %% @spec stream_close(Req_id :: req_id()) -> ok | {error, unknown_req_id} stream_close(Req_id) -> - case ets:lookup(ibrowse_stream, {req_id_pid, Req_id}) of + case ets:lookup(?STREAM_TABLE, {req_id_pid, Req_id}) of [] -> {error, unknown_req_id}; [{_, Pid}] -> @@ -747,8 +747,8 @@ init(_) -> put(my_trace_flag, State#state.trace), put(ibrowse_trace_token, "ibrowse"), ?LOAD_BALANCER_NAMED_TABLE = ets:new(?LOAD_BALANCER_NAMED_TABLE, [named_table, public, {keypos, 2}]), - ibrowse_conf = ets:new(ibrowse_conf, [named_table, protected, {keypos, 2}]), - ibrowse_stream = ets:new(ibrowse_stream, [named_table, public]), + ?CONF_TABLE = ets:new(?CONF_TABLE, [named_table, protected, {keypos, 2}]), + ?STREAM_TABLE = ets:new(?STREAM_TABLE, [named_table, public]), import_config(), {ok, #state{}}. @@ -770,7 +770,7 @@ import_config(Filename) -> end. apply_config(Terms) -> - ets:delete_all_objects(ibrowse_conf), + ets:delete_all_objects(?CONF_TABLE), insert_config(Terms). insert_config(Terms) -> @@ -783,12 +783,12 @@ insert_config(Terms) -> {{options, Host, Port}, Options}], lists:foreach( fun({X, Y}) -> - ets:insert(ibrowse_conf, + ets:insert(?CONF_TABLE, #ibrowse_conf{key = X, value = Y}) end, I); ({K, V}) -> - ets:insert(ibrowse_conf, + ets:insert(?CONF_TABLE, #ibrowse_conf{key = K, value = V}); (X) -> @@ -799,7 +799,7 @@ insert_config(Terms) -> %% @doc Internal export get_config_value(Key) -> try - [#ibrowse_conf{value = V}] = ets:lookup(ibrowse_conf, Key), + [#ibrowse_conf{value = V}] = ets:lookup(?CONF_TABLE, Key), V catch error:badarg -> @@ -809,7 +809,7 @@ get_config_value(Key) -> %% @doc Internal export get_config_value(Key, DefVal) -> try - case ets:lookup(ibrowse_conf, Key) of + case ets:lookup(?CONF_TABLE, Key) of [] -> DefVal; [#ibrowse_conf{value = V}] -> @@ -821,7 +821,7 @@ get_config_value(Key, DefVal) -> end. set_config_value(Key, Val) -> - ets:insert(ibrowse_conf, #ibrowse_conf{key = Key, value = Val}). + ets:insert(?CONF_TABLE, #ibrowse_conf{key = Key, value = Val}). %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages @@ -888,7 +888,7 @@ handle_cast(_Msg, State) -> %%-------------------------------------------------------------------- handle_info(all_trace_off, State) -> Mspec = [{{ibrowse_conf,{trace,'$1','$2'},true},[],[{{'$1','$2'}}]}], - Trace_on_dests = ets:select(ibrowse_conf, Mspec), + Trace_on_dests = ets:select(?CONF_TABLE, Mspec), Fun = fun(#lb_pid{host_port = {H, P}, pid = Pid}, _) -> case lists:member({H, P}, Trace_on_dests) of false -> @@ -900,7 +900,7 @@ handle_info(all_trace_off, State) -> Acc end, ets:foldl(Fun, undefined, ?LOAD_BALANCER_NAMED_TABLE), - ets:select_delete(ibrowse_conf, [{{ibrowse_conf,{trace,'$1','$2'},true},[],['true']}]), + ets:select_delete(?CONF_TABLE, [{{ibrowse_conf,{trace,'$1','$2'},true},[],['true']}]), {noreply, State}; handle_info({trace, Bool}, State) -> @@ -916,7 +916,7 @@ handle_info({trace, Bool, Host, Port}, State) -> Acc end, ets:foldl(Fun, undefined, ?LOAD_BALANCER_NAMED_TABLE), - ets:insert(ibrowse_conf, #ibrowse_conf{key = {trace, Host, Port}, + ets:insert(?CONF_TABLE, #ibrowse_conf{key = {trace, Host, Port}, value = Bool}), {noreply, State}; diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index ad24351..c4154ab 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -801,7 +801,7 @@ send_req_1(From, {Caller, once} when is_pid(Caller) or is_atom(Caller) -> Async_pid_rec = {{req_id_pid, ReqId}, self()}, - true = ets:insert(ibrowse_stream, Async_pid_rec), + true = ets:insert(?STREAM_TABLE, Async_pid_rec), {Caller, true}; undefined -> {undefined, false}; @@ -1835,7 +1835,7 @@ do_reply(#state{prev_req_id = Prev_req_id} = State, %% stream_once and sync requests on the same connection, it will %% take a while for the req_id-pid mapping to get cleared, but it %% should do no harm. - ets:delete(ibrowse_stream, {req_id_pid, Prev_req_id}), + ets:delete(?STREAM_TABLE, {req_id_pid, Prev_req_id}), State_1#state{prev_req_id = ReqId}; do_reply(State, _From, StreamTo, ReqId, Resp_format, Msg) -> State_1 = dec_pipeline_counter(State), @@ -1853,7 +1853,7 @@ do_error_reply(#state{reqs = Reqs, tunnel_setup_queue = Tun_q} = State, Err) -> ReqList = queue:to_list(Reqs), lists:foreach(fun(#request{from=From, stream_to=StreamTo, req_id=ReqId, response_format = Resp_format}) -> - ets:delete(ibrowse_stream, {req_id_pid, ReqId}), + ets:delete(?STREAM_TABLE, {req_id_pid, ReqId}), do_reply(State, From, StreamTo, ReqId, Resp_format, {error, Err}) end, ReqList), lists:foreach( From 725d503ce573530cb5c6392cd180934c3792af24 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Tue, 18 Nov 2014 22:48:45 +0000 Subject: [PATCH 21/45] Updated local rebar to fix problem with eunit Prior version was "2". New functional tests suite was not being picked up by local rebar, but was execute with my local which is version "2.5.2" which targets the same version of Erlang/OTP as iBrowse (R16). --- rebar | Bin 90778 -> 188026 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rebar b/rebar index b9c73ff1805e4dbfaff884678b54c3164ebe8294..8e4deb6d0888612c4f54b17e1d87b1874a0cf25b 100755 GIT binary patch literal 188026 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%0Dq@*pzEtnXkMIqNOj+gm^hkiUqCUTOCgn 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 z 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Ⓢ6bR@HJyqt0iPF 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 LueyL0WRU*{X$3r8 literal 90778 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 z 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 Date: Wed, 19 Nov 2014 04:09:09 +0000 Subject: [PATCH 22/45] Removed unneeded initial table creation Verified that all code paths using saved table identifier from state are guarded either by a check for undefined or a call to maybe_create_ets, so the table should always be there when it is needed without creating before real use. --- src/ibrowse_lb.erl | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 9f49e0d..ce5e398 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -70,13 +70,11 @@ init([Host, Port]) -> Max_pipe_sz = ibrowse:get_config_value({max_pipeline_size, Host, Port}, 10), put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)), put(ibrowse_trace_token, ["LB: ", Host, $:, integer_to_list(Port)]), - Tid = ets:new(?CONNECTIONS_LOCAL_TABLE, [public, ordered_set]), {ok, #state{parent_pid = whereis(ibrowse), - host = Host, - port = Port, - ets_tid = Tid, - max_pipeline_size = Max_pipe_sz, - max_sessions = Max_sessions}}. + host = Host, + port = Port, + max_pipeline_size = Max_pipe_sz, + max_sessions = Max_sessions}}. spawn_connection(Lb_pid, Url, Max_sessions, @@ -111,7 +109,6 @@ stop(Lb_pid) -> handle_call(stop, _From, #state{ets_tid = undefined} = State) -> gen_server:reply(_From, ok), {stop, normal, State}; - handle_call(stop, _From, #state{ets_tid = Tid} = State) -> ets:foldl(fun({Pid, _, _}, Acc) -> ibrowse_http_client:stop(Pid), @@ -165,13 +162,9 @@ handle_cast(_Msg, State) -> %%-------------------------------------------------------------------- handle_info({'EXIT', Parent, _Reason}, #state{parent_pid = Parent} = State) -> {stop, normal, State}; - handle_info({'EXIT', _Pid, _Reason}, #state{ets_tid = undefined} = State) -> {noreply, State}; - -handle_info({'EXIT', Pid, _Reason}, - #state{num_cur_sessions = Cur, - ets_tid = Tid} = State) -> +handle_info({'EXIT', Pid, _Reason}, #state{num_cur_sessions = Cur, ets_tid = Tid} = State) -> ets:match_delete(Tid, {{'_', Pid}, '_'}), Cur_1 = Cur - 1, case Cur_1 of @@ -185,7 +178,6 @@ handle_info({'EXIT', Pid, _Reason}, handle_info({trace, Bool}, #state{ets_tid = undefined} = State) -> put(my_trace_flag, Bool), {noreply, State}; - handle_info({trace, Bool}, #state{ets_tid = Tid} = State) -> ets:foldl(fun({{_, Pid}, _}, Acc) when is_pid(Pid) -> catch Pid ! {trace, Bool}, From b5b382a77a546c0439b7caa20618d350a7618075 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 15:32:40 +0000 Subject: [PATCH 23/45] General cleanup of spacing and order Preparing to rework the pipelining algorithm --- src/ibrowse_lb.erl | 91 +++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index ce5e398..174e50e 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -5,19 +5,16 @@ %%% %%% Created : 6 Mar 2008 by chandru %%%------------------------------------------------------------------- + -module(ibrowse_lb). -author(chandru). -behaviour(gen_server). -%%-------------------------------------------------------------------- -%% Include files -%%-------------------------------------------------------------------- -%%-------------------------------------------------------------------- %% External exports -export([ start_link/1, spawn_connection/6, - stop/1 + stop/1 ]). %% gen_server callbacks @@ -31,14 +28,13 @@ ]). -record(state, {parent_pid, - ets_tid, - host, - port, - max_sessions, - max_pipeline_size, - num_cur_sessions = 0, - proc_state - }). + ets_tid, + host, + port, + max_sessions, + max_pipeline_size, + num_cur_sessions = 0, + proc_state}). -include("ibrowse.hrl"). @@ -52,10 +48,30 @@ start_link(Args) -> gen_server:start_link(?MODULE, Args, []). +spawn_connection(Lb_pid, + Url, + Max_sessions, + Max_pipeline_size, + SSL_options, + Process_options) + when is_pid(Lb_pid), + is_record(Url, url), + is_integer(Max_pipeline_size), + is_integer(Max_sessions) -> + gen_server:call(Lb_pid, + {spawn_connection, Url, Max_sessions, Max_pipeline_size, SSL_options, Process_options}). + +stop(Lb_pid) -> + case catch gen_server:call(Lb_pid, stop) of + {'EXIT', {timeout, _}} -> + exit(Lb_pid, kill); + ok -> + ok + end. + %%==================================================================== %% Server functions %%==================================================================== - %%-------------------------------------------------------------------- %% Function: init/1 %% Description: Initiates the server @@ -66,35 +82,19 @@ start_link(Args) -> %%-------------------------------------------------------------------- init([Host, Port]) -> process_flag(trap_exit, true), + Max_sessions = ibrowse:get_config_value({max_sessions, Host, Port}, 10), Max_pipe_sz = ibrowse:get_config_value({max_pipeline_size, Host, Port}, 10), + put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)), put(ibrowse_trace_token, ["LB: ", Host, $:, integer_to_list(Port)]), + {ok, #state{parent_pid = whereis(ibrowse), host = Host, port = Port, max_pipeline_size = Max_pipe_sz, max_sessions = Max_sessions}}. -spawn_connection(Lb_pid, Url, - Max_sessions, - Max_pipeline_size, - SSL_options, - Process_options) - when is_pid(Lb_pid), - is_record(Url, url), - is_integer(Max_pipeline_size), - is_integer(Max_sessions) -> - gen_server:call(Lb_pid, - {spawn_connection, Url, Max_sessions, Max_pipeline_size, SSL_options, Process_options}). - -stop(Lb_pid) -> - case catch gen_server:call(Lb_pid, stop) of - {'EXIT', {timeout, _}} -> - exit(Lb_pid, kill); - ok -> - ok - end. %%-------------------------------------------------------------------- %% Function: handle_call/3 %% Description: Handling call messages @@ -105,14 +105,13 @@ stop(Lb_pid) -> %% {stop, Reason, Reply, State} | (terminate/2 is called) %% {stop, Reason, State} (terminate/2 is called) %%-------------------------------------------------------------------- - handle_call(stop, _From, #state{ets_tid = undefined} = State) -> gen_server:reply(_From, ok), {stop, normal, State}; handle_call(stop, _From, #state{ets_tid = Tid} = State) -> ets:foldl(fun({Pid, _, _}, Acc) -> - ibrowse_http_client:stop(Pid), - Acc + ibrowse_http_client:stop(Pid), + Acc end, [], Tid), gen_server:reply(_From, ok), {stop, normal, State}; @@ -122,15 +121,14 @@ handle_call(_, _From, #state{proc_state = shutting_down} = State) -> %% Update max_sessions in #state with supplied value handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _, _}, _From, - #state{num_cur_sessions = Num} = State) - when Num >= Max_sess -> + #state{num_cur_sessions = Num} = State) + when Num >= Max_sess -> State_1 = maybe_create_ets(State), Reply = find_best_connection(State_1#state.ets_tid, Max_pipe), - {reply, Reply, State_1#state{max_sessions = Max_sess, - max_pipeline_size = Max_pipe}}; + {reply, Reply, State_1#state{max_sessions = Max_sess, max_pipeline_size = Max_pipe}}; handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From, - #state{num_cur_sessions = Cur} = State) -> + #state{num_cur_sessions = Cur} = State) -> State_1 = maybe_create_ets(State), Tid = State_1#state.ets_tid, {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options), @@ -180,11 +178,11 @@ handle_info({trace, Bool}, #state{ets_tid = undefined} = State) -> {noreply, State}; handle_info({trace, Bool}, #state{ets_tid = Tid} = State) -> ets:foldl(fun({{_, Pid}, _}, Acc) when is_pid(Pid) -> - catch Pid ! {trace, Bool}, - Acc; - (_, Acc) -> - Acc - end, undefined, Tid), + catch Pid ! {trace, Bool}, + Acc; + (_, Acc) -> + Acc + end, undefined, Tid), put(my_trace_flag, Bool), {noreply, State}; @@ -253,3 +251,4 @@ maybe_create_ets(#state{ets_tid = undefined} = State) -> State#state{ets_tid = Tid}; maybe_create_ets(State) -> State. + From 5d11a7c649c49e765f64993ee0193bfb53cd32cd Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 15:47:45 +0000 Subject: [PATCH 24/45] Removed unneccessary safe_fixtable for ordered set Via ets man page: Note that for tables of the ordered_set type, safe_fixtable/2 is not necessary as calls to first/1 and next/2 will always succeed --- src/ibrowse_lb.erl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 174e50e..d3cd1b9 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -224,10 +224,7 @@ code_change(_OldVsn, State, _Extra) -> %%% Internal functions %%-------------------------------------------------------------------- find_best_connection(Tid, Max_pipe) -> - ets:safe_fixtable(Tid, true), - Res = find_best_connection(ets:first(Tid), Tid, Max_pipe), - ets:safe_fixtable(Tid, false), - Res. + find_best_connection(ets:first(Tid), Tid, Max_pipe). find_best_connection('$end_of_table', _, _) -> {error, retry_later}; From 8ee3444c14bc156198b0dc01ed50e6ea15a19970 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 17:12:20 +0000 Subject: [PATCH 25/45] Replaced local size in state with ets lookup Got rid of duplicated information in order to reduce complexity and change points for upcoming algorithm changes. --- src/ibrowse_lb.erl | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index d3cd1b9..1d7df44 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -33,7 +33,6 @@ port, max_sessions, max_pipeline_size, - num_cur_sessions = 0, proc_state}). -include("ibrowse.hrl"). @@ -119,23 +118,18 @@ handle_call(stop, _From, #state{ets_tid = Tid} = State) -> handle_call(_, _From, #state{proc_state = shutting_down} = State) -> {reply, {error, shutting_down}, State}; -%% Update max_sessions in #state with supplied value -handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _, _}, _From, - #state{num_cur_sessions = Num} = State) - when Num >= Max_sess -> - State_1 = maybe_create_ets(State), - Reply = find_best_connection(State_1#state.ets_tid, Max_pipe), - {reply, Reply, State_1#state{max_sessions = Max_sess, max_pipeline_size = Max_pipe}}; - -handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From, - #state{num_cur_sessions = Cur} = State) -> +handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From, State) -> State_1 = maybe_create_ets(State), Tid = State_1#state.ets_tid, - {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options), - ets:insert(Tid, {Pid, 0, 0}), - {reply, {ok, Pid}, State_1#state{num_cur_sessions = Cur + 1, - max_sessions = Max_sess, - max_pipeline_size = Max_pipe}}; + Reply = case ets:info(Tid, size) of + X when X >= Max_sess -> + find_best_connection(Tid, Max_pipe); + _ -> + Result = {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options), + ets:insert(Tid, {Pid, 0, 0}), + Result + end, + {reply, Reply, State_1#state{max_sessions = Max_sess, max_pipeline_size = Max_pipe}}; handle_call(Request, _From, State) -> Reply = {unknown_request, Request}, @@ -162,15 +156,14 @@ handle_info({'EXIT', Parent, _Reason}, #state{parent_pid = Parent} = State) -> {stop, normal, State}; handle_info({'EXIT', _Pid, _Reason}, #state{ets_tid = undefined} = State) -> {noreply, State}; -handle_info({'EXIT', Pid, _Reason}, #state{num_cur_sessions = Cur, ets_tid = Tid} = State) -> +handle_info({'EXIT', Pid, _Reason}, #state{ets_tid = Tid} = State) -> ets:match_delete(Tid, {{'_', Pid}, '_'}), - Cur_1 = Cur - 1, - case Cur_1 of + case ets:info(Tid, size) of 0 -> ets:delete(Tid), - {noreply, State#state{ets_tid = undefined, num_cur_sessions = 0}, 10000}; + {noreply, State#state{ets_tid = undefined}, 10000}; _ -> - {noreply, State#state{num_cur_sessions = Cur_1}} + {noreply, State} end; handle_info({trace, Bool}, #state{ets_tid = undefined} = State) -> From c14e322599e2415be23507ae4e013120c2103452 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 18:10:07 +0000 Subject: [PATCH 26/45] Removed unnecessary pipeline size from con state Tracking size in http connection process state was unnecessary as it wasn't queried for logic or obvious reporting and ets table should have right (within 1) value already. --- src/ibrowse_http_client.erl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index c4154ab..6628427 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -53,7 +53,7 @@ deleted_crlf = false, transfer_encoding, chunk_size, chunk_size_buffer = <<>>, recvd_chunk_size, interim_reply_sent = false, - lb_ets_tid, cur_pipeline_size = 0, prev_req_id + lb_ets_tid, prev_req_id }). -record(request, {url, method, options, from, @@ -1938,18 +1938,16 @@ to_lower([], Acc) -> shutting_down(#state{lb_ets_tid = undefined}) -> ok; -shutting_down(#state{lb_ets_tid = Tid, - cur_pipeline_size = _Sz}) -> +shutting_down(#state{lb_ets_tid = Tid}) -> catch ets:delete(Tid, self()). inc_pipeline_counter(#state{is_closing = true} = State) -> State; inc_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> State; -inc_pipeline_counter(#state{cur_pipeline_size = Pipe_sz, - lb_ets_tid = Tid} = State) -> +inc_pipeline_counter(#state{lb_ets_tid = Tid} = State) -> update_counter(Tid, self(), {2,1,99999,9999}), - State#state{cur_pipeline_size = Pipe_sz + 1}. + State. update_counter(Tid, Key, Args) -> ets:update_counter(Tid, Key, Args). @@ -1958,8 +1956,7 @@ dec_pipeline_counter(#state{is_closing = true} = State) -> State; dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> State; -dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz, - lb_ets_tid = Tid} = State) -> +dec_pipeline_counter(#state{lb_ets_tid = Tid} = State) -> _ = try update_counter(Tid, self(), {2,-1,0,0}), update_counter(Tid, self(), {3,-1,0,0}) @@ -1967,7 +1964,7 @@ dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz, _:_ -> ok end, - State#state{cur_pipeline_size = Pipe_sz - 1}. + State. flatten([H | _] = L) when is_integer(H) -> L; From 38242f4fd076acaeb012cf755afc9fd25b924c0d Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 18:21:49 +0000 Subject: [PATCH 27/45] Encapsulated conn ets table use in ibrowse_lb Moved all interactions with ets table tracking connections to API functions on ibrowse_lb, to reduce knowledge of ets table and its structure. Also fixed "bug" where ceiling for pipelining threshold was different than the set value for increment; made them the same. --- src/ibrowse_http_client.erl | 15 +++------------ src/ibrowse_lb.erl | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index 6628427..7818a15 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -1939,31 +1939,22 @@ to_lower([], Acc) -> shutting_down(#state{lb_ets_tid = undefined}) -> ok; shutting_down(#state{lb_ets_tid = Tid}) -> - catch ets:delete(Tid, self()). + ibrowse_lb:report_connection_down(Tid). inc_pipeline_counter(#state{is_closing = true} = State) -> State; inc_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> State; inc_pipeline_counter(#state{lb_ets_tid = Tid} = State) -> - update_counter(Tid, self(), {2,1,99999,9999}), + ibrowse_lb:report_request_underway(Tid), State. -update_counter(Tid, Key, Args) -> - ets:update_counter(Tid, Key, Args). - dec_pipeline_counter(#state{is_closing = true} = State) -> State; dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> State; dec_pipeline_counter(#state{lb_ets_tid = Tid} = State) -> - _ = try - update_counter(Tid, self(), {2,-1,0,0}), - update_counter(Tid, self(), {3,-1,0,0}) - catch - _:_ -> - ok - end, + ibrowse_lb:report_request_complete(Tid), State. flatten([H | _] = L) when is_integer(H) -> diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 1d7df44..34f64f0 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -14,7 +14,10 @@ -export([ start_link/1, spawn_connection/6, - stop/1 + stop/1, + report_connection_down/1, + report_request_underway/1, + report_request_complete/1 ]). %% gen_server callbacks @@ -68,6 +71,16 @@ stop(Lb_pid) -> ok end. +report_connection_down(Tid) -> + catch ets:delete(Tid, self()). + +report_request_underway(Tid) -> + catch ets:update_counter(Tid, self(), {2, 1, 9999, 9999}). + +report_request_complete(Tid) -> + catch ets:update_counter(Tid, self(), {2, -1, 0, 0}), + catch ets:update_counter(Tid, self(), {3, -1, 0, 0}). + %%==================================================================== %% Server functions %%==================================================================== From 13b4f6de25df881075875417065eaf552e8316d8 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 19:04:51 +0000 Subject: [PATCH 28/45] Connection now has whole responsibility of cleanup HTTP connection process now manages both scenarios requiring cleaning up of the load balancer's ets table; instead of this being owned by both the conn and lb. --- src/ibrowse_http_client.erl | 1 + src/ibrowse_lb.erl | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index 7818a15..68e0ae3 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -266,6 +266,7 @@ handle_info(Info, State) -> %%-------------------------------------------------------------------- terminate(_Reason, State) -> do_close(State), + shutting_down(State), ok. %%-------------------------------------------------------------------- diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 34f64f0..297c96b 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -167,18 +167,6 @@ handle_cast(_Msg, State) -> %%-------------------------------------------------------------------- handle_info({'EXIT', Parent, _Reason}, #state{parent_pid = Parent} = State) -> {stop, normal, State}; -handle_info({'EXIT', _Pid, _Reason}, #state{ets_tid = undefined} = State) -> - {noreply, State}; -handle_info({'EXIT', Pid, _Reason}, #state{ets_tid = Tid} = State) -> - ets:match_delete(Tid, {{'_', Pid}, '_'}), - case ets:info(Tid, size) of - 0 -> - ets:delete(Tid), - {noreply, State#state{ets_tid = undefined}, 10000}; - _ -> - {noreply, State} - end; - handle_info({trace, Bool}, #state{ets_tid = undefined} = State) -> put(my_trace_flag, Bool), {noreply, State}; From e7a7e721d16cf20c6f63feb813d562578f8b8fc6 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 19:20:42 +0000 Subject: [PATCH 29/45] Encapsulated remaining interactions with conn ets Created better abstractions on current ets table layout to communicate what we want to accomplish, not how. --- src/ibrowse_lb.erl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 297c96b..3c17691 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -134,12 +134,12 @@ handle_call(_, _From, #state{proc_state = shutting_down} = State) -> handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From, State) -> State_1 = maybe_create_ets(State), Tid = State_1#state.ets_tid, - Reply = case ets:info(Tid, size) of + Reply = case num_current_connections(Tid) of X when X >= Max_sess -> find_best_connection(Tid, Max_pipe); _ -> Result = {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options), - ets:insert(Tid, {Pid, 0, 0}), + record_new_connection(Tid, Pid), Result end, {reply, Reply, State_1#state{max_sessions = Max_sess, max_pipeline_size = Max_pipe}}; @@ -226,7 +226,7 @@ find_best_connection(Pid, Tid, Max_pipe) -> case ets:lookup(Tid, Pid) of [{Pid, Cur_sz, Speculative_sz}] when Cur_sz < Max_pipe, Speculative_sz < Max_pipe -> - case catch ets:update_counter(Tid, Pid, {3, 1, 9999999, 9999999}) of + case record_request_for_connection(Tid, Pid) of {'EXIT', _} -> %% The selected process has shutdown find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe); @@ -243,3 +243,12 @@ maybe_create_ets(#state{ets_tid = undefined} = State) -> maybe_create_ets(State) -> State. +%% Ets connection table utility methods +num_current_connections(Tid) -> + catch ets:info(Tid, size). + +record_new_connection(Tid, Pid) -> + catch ets:insert(Tid, {Pid, 0, 0}). + +record_request_for_connection(Tid, Pid) -> + catch ets:update_counter(Tid, Pid, {3, 1, 9999999, 9999999}). From 8b94a527f933b4a1c17fbb0d83f20ec97ce525aa Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 19:26:54 +0000 Subject: [PATCH 30/45] Removed speculative sizing and enforced common max Difference between speculative and "real" size was no longer material to the algorithm. Used macro to enforce consistent usage of ceiling for pipeline. --- src/ibrowse_lb.erl | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 3c17691..1e644eb 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -38,6 +38,8 @@ max_pipeline_size, proc_state}). +-define(PIPELINE_MAX, 99999). + -include("ibrowse.hrl"). %%==================================================================== @@ -75,11 +77,10 @@ report_connection_down(Tid) -> catch ets:delete(Tid, self()). report_request_underway(Tid) -> - catch ets:update_counter(Tid, self(), {2, 1, 9999, 9999}). + catch ets:update_counter(Tid, self(), {2, 1, ?PIPELINE_MAX, ?PIPELINE_MAX}). report_request_complete(Tid) -> - catch ets:update_counter(Tid, self(), {2, -1, 0, 0}), - catch ets:update_counter(Tid, self(), {3, -1, 0, 0}). + catch ets:update_counter(Tid, self(), {2, -1, 0, 0}). %%==================================================================== %% Server functions @@ -121,7 +122,7 @@ handle_call(stop, _From, #state{ets_tid = undefined} = State) -> gen_server:reply(_From, ok), {stop, normal, State}; handle_call(stop, _From, #state{ets_tid = Tid} = State) -> - ets:foldl(fun({Pid, _, _}, Acc) -> + ets:foldl(fun({Pid, _}, Acc) -> ibrowse_http_client:stop(Pid), Acc end, [], Tid), @@ -171,10 +172,8 @@ handle_info({trace, Bool}, #state{ets_tid = undefined} = State) -> put(my_trace_flag, Bool), {noreply, State}; handle_info({trace, Bool}, #state{ets_tid = Tid} = State) -> - ets:foldl(fun({{_, Pid}, _}, Acc) when is_pid(Pid) -> + ets:foldl(fun({Pid, _}, Acc) when is_pid(Pid) -> catch Pid ! {trace, Bool}, - Acc; - (_, Acc) -> Acc end, undefined, Tid), put(my_trace_flag, Bool), @@ -224,8 +223,7 @@ find_best_connection('$end_of_table', _, _) -> {error, retry_later}; find_best_connection(Pid, Tid, Max_pipe) -> case ets:lookup(Tid, Pid) of - [{Pid, Cur_sz, Speculative_sz}] when Cur_sz < Max_pipe, - Speculative_sz < Max_pipe -> + [{Pid, Cur_sz}] when Cur_sz < Max_pipe -> case record_request_for_connection(Tid, Pid) of {'EXIT', _} -> %% The selected process has shutdown @@ -248,7 +246,7 @@ num_current_connections(Tid) -> catch ets:info(Tid, size). record_new_connection(Tid, Pid) -> - catch ets:insert(Tid, {Pid, 0, 0}). + catch ets:insert(Tid, {Pid, 0}). record_request_for_connection(Tid, Pid) -> - catch ets:update_counter(Tid, Pid, {3, 1, 9999999, 9999999}). + catch ets:update_counter(Tid, Pid, {2, 1, ?PIPELINE_MAX, ?PIPELINE_MAX}). From 3061aa2fd95cdf07970589a5c988bdc7e7d760d7 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 19:48:32 +0000 Subject: [PATCH 31/45] Encapsulated the iteration of connections and msgs Use of foldl for iteration is not hidden as implementation detail. Also hid details of how to message conn to set tracing within API. --- src/ibrowse_http_client.erl | 4 ++++ src/ibrowse_lb.erl | 14 ++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index 68e0ae3..1bb95d2 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -19,6 +19,7 @@ start/1, start/2, stop/1, + trace/2, send_req/7 ]). @@ -101,6 +102,9 @@ stop(Conn_pid) -> ok end. +trace(Conn_pid, Bool) -> + catch Conn_pid ! {trace, Bool}. + send_req(Conn_Pid, Url, Headers, Method, Body, Options, Timeout) -> gen_server:call( Conn_Pid, diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 1e644eb..cc067fc 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -122,10 +122,7 @@ handle_call(stop, _From, #state{ets_tid = undefined} = State) -> gen_server:reply(_From, ok), {stop, normal, State}; handle_call(stop, _From, #state{ets_tid = Tid} = State) -> - ets:foldl(fun({Pid, _}, Acc) -> - ibrowse_http_client:stop(Pid), - Acc - end, [], Tid), + for_each_connection_pid(Tid, fun(Pid) -> ibrowse_http_client:stop(Pid) end), gen_server:reply(_From, ok), {stop, normal, State}; @@ -172,10 +169,7 @@ handle_info({trace, Bool}, #state{ets_tid = undefined} = State) -> put(my_trace_flag, Bool), {noreply, State}; handle_info({trace, Bool}, #state{ets_tid = Tid} = State) -> - ets:foldl(fun({Pid, _}, Acc) when is_pid(Pid) -> - catch Pid ! {trace, Bool}, - Acc - end, undefined, Tid), + for_each_connection_pid(Tid, fun(Pid) -> ibrowse_http_client:trace(Pid, Bool) end), put(my_trace_flag, Bool), {noreply, State}; @@ -250,3 +244,7 @@ record_new_connection(Tid, Pid) -> record_request_for_connection(Tid, Pid) -> catch ets:update_counter(Tid, Pid, {2, 1, ?PIPELINE_MAX, ?PIPELINE_MAX}). + +for_each_connection_pid(Tid, Fun) -> + catch ets:foldl(fun({Pid, _}, _) -> Fun(Pid) end, undefined, Tid), + ok. From 9d0b7e3eea12a72ae619e6f34aab349b25893eef Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Wed, 19 Nov 2014 21:50:54 +0000 Subject: [PATCH 32/45] Changed pipeline algo to smallest pipeline first Big commit. Switched algorithm to one which will favor the connection with the smallest pipeline first (deciding ties by timestamp of last finished request, and then by pid as ultimate tie breaker). Note: this also drastically changes the internal representation of the connection in ets and is dependent on specific order of operations when changing key values to limit risk of race conditions between loadbalancer and a given connection. Also removed connection reporting of start of request as this was no longer necessary since the load balancer tees up the entry into ets with a 1. --- src/ibrowse_http_client.erl | 44 ++++++++----------- src/ibrowse_lb.erl | 71 ++++++++++++++++++++----------- test/ibrowse_functional_tests.erl | 11 ++--- 3 files changed, 66 insertions(+), 60 deletions(-) diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index 1bb95d2..c9161b0 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -762,11 +762,10 @@ send_req_1(From, {ok, _Sent_body} -> trace_request_body(Body_1), _ = active_once(State_1), - State_1_1 = inc_pipeline_counter(State_1), - State_2 = State_1_1#state{status = get_header, - cur_req = NewReq, - proxy_tunnel_setup = in_progress, - tunnel_setup_queue = [{From, Url, Headers, Method, Body, Options, Timeout}]}, + State_2 = State_1#state{status = get_header, + cur_req = NewReq, + proxy_tunnel_setup = in_progress, + tunnel_setup_queue = [{From, Url, Headers, Method, Body, Options, Timeout}]}, State_3 = set_inac_timer(State_2), {noreply, State_3}; Err -> @@ -853,15 +852,14 @@ send_req_1(From, Raw_req = list_to_binary([Req, Sent_body]), NewReq_1 = NewReq#request{raw_req = Raw_req}, State_1 = State#state{reqs=queue:in(NewReq_1, State#state.reqs)}, - State_2 = inc_pipeline_counter(State_1), - _ = active_once(State_2), - State_3 = case Status of + _ = active_once(State_1), + State_2 = case Status of idle -> - State_2#state{ + State_1#state{ status = get_header, cur_req = NewReq_1}; _ -> - State_2 + State_1 end, case StreamTo of undefined -> @@ -875,8 +873,8 @@ send_req_1(From, catch StreamTo ! {ibrowse_async_raw_req, Raw_req} end end, - State_4 = set_inac_timer(State_3), - {noreply, State_4}; + State_3 = set_inac_timer(State_2), + {noreply, State_3}; Err -> shutting_down(State), do_trace("Send failed... Reason: ~p~n", [Err]), @@ -1815,13 +1813,13 @@ format_response_data(Resp_format, Body) -> do_reply(State, From, undefined, _, Resp_format, {ok, St_code, Headers, Body}) -> Msg_1 = {ok, St_code, Headers, format_response_data(Resp_format, Body)}, gen_server:reply(From, Msg_1), - dec_pipeline_counter(State); + report_request_complete(State); do_reply(State, From, undefined, _, _, Msg) -> gen_server:reply(From, Msg), - dec_pipeline_counter(State); + report_request_complete(State); do_reply(#state{prev_req_id = Prev_req_id} = State, _From, StreamTo, ReqId, Resp_format, {ok, _, _, Body}) -> - State_1 = dec_pipeline_counter(State), + State_1 = report_request_complete(State), case Body of [] -> ok; @@ -1843,7 +1841,7 @@ do_reply(#state{prev_req_id = Prev_req_id} = State, ets:delete(?STREAM_TABLE, {req_id_pid, Prev_req_id}), State_1#state{prev_req_id = ReqId}; do_reply(State, _From, StreamTo, ReqId, Resp_format, Msg) -> - State_1 = dec_pipeline_counter(State), + State_1 = report_request_complete(State), Msg_1 = format_response_data(Resp_format, Msg), catch StreamTo ! {ibrowse_async_response, ReqId, Msg_1}, State_1. @@ -1946,19 +1944,11 @@ shutting_down(#state{lb_ets_tid = undefined}) -> shutting_down(#state{lb_ets_tid = Tid}) -> ibrowse_lb:report_connection_down(Tid). -inc_pipeline_counter(#state{is_closing = true} = State) -> - State; -inc_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> - State; -inc_pipeline_counter(#state{lb_ets_tid = Tid} = State) -> - ibrowse_lb:report_request_underway(Tid), - State. - -dec_pipeline_counter(#state{is_closing = true} = State) -> +report_request_complete(#state{is_closing = true} = State) -> State; -dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) -> +report_request_complete(#state{lb_ets_tid = undefined} = State) -> State; -dec_pipeline_counter(#state{lb_ets_tid = Tid} = State) -> +report_request_complete(#state{lb_ets_tid = Tid} = State) -> ibrowse_lb:report_request_complete(Tid), State. diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index cc067fc..3d487d4 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -16,7 +16,6 @@ spawn_connection/6, stop/1, report_connection_down/1, - report_request_underway/1, report_request_complete/1 ]). @@ -39,6 +38,9 @@ proc_state}). -define(PIPELINE_MAX, 99999). +-define(KEY_MATCHSPEC_BY_PID(Pid), [{{{'_', '_', Pid}, '_'}, [], ['$_']}]). +-define(KEY_MATCHSPEC(Key), [{{Key, '_'}, [], ['$_']}]). +-define(KEY_MATCHSPEC_FOR_DELETE(Key), [{{Key, '_'}, [], [true]}]). -include("ibrowse.hrl"). @@ -74,13 +76,23 @@ stop(Lb_pid) -> end. report_connection_down(Tid) -> - catch ets:delete(Tid, self()). - -report_request_underway(Tid) -> - catch ets:update_counter(Tid, self(), {2, 1, ?PIPELINE_MAX, ?PIPELINE_MAX}). + %% Don't cascade errors since Tid is really managed by other process + catch ets:select_delete(Tid, ?KEY_MATCHSPEC_BY_PID(self())). report_request_complete(Tid) -> - catch ets:update_counter(Tid, self(), {2, -1, 0, 0}). + %% Don't cascade errors since Tid is really managed by other process + catch case ets:select(Tid, ?KEY_MATCHSPEC_BY_PID(self())) of + [MatchKey] -> + case ets:select_delete(Tid, ?KEY_MATCHSPEC_FOR_DELETE(MatchKey)) of + 1 -> + ets:insert(Tid, {decremented(MatchKey), undefined}), + true; + _ -> + false + end; + _ -> + false + end. %%==================================================================== %% Server functions @@ -210,23 +222,17 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -find_best_connection(Tid, Max_pipe) -> - find_best_connection(ets:first(Tid), Tid, Max_pipe). - -find_best_connection('$end_of_table', _, _) -> - {error, retry_later}; -find_best_connection(Pid, Tid, Max_pipe) -> - case ets:lookup(Tid, Pid) of - [{Pid, Cur_sz}] when Cur_sz < Max_pipe -> - case record_request_for_connection(Tid, Pid) of - {'EXIT', _} -> - %% The selected process has shutdown - find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe); - _ -> - {ok, Pid} +find_best_connection(Tid, Max_pipeline_size) -> + case ets:first(Tid) of + {Size, _Timestamp, Pid} = Key when Size < Max_pipeline_size -> + case record_request_for_connection(Tid, Key) of + true -> + {ok, Pid}; + false -> + find_best_connection(Tid, Max_pipeline_size) end; - _ -> - find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe) + _ -> + {error, retry_later} end. maybe_create_ets(#state{ets_tid = undefined} = State) -> @@ -240,10 +246,25 @@ num_current_connections(Tid) -> catch ets:info(Tid, size). record_new_connection(Tid, Pid) -> - catch ets:insert(Tid, {Pid, 0}). + catch ets:insert(Tid, {new_key(Pid), undefined}). + +record_request_for_connection(Tid, Key) -> + case ets:select_delete(Tid, ?KEY_MATCHSPEC_FOR_DELETE(Key)) of + 1 -> + ets:insert(Tid, {incremented(Key), undefined}), + true; + _ -> + false + end. + +new_key(Pid) -> + {1, os:timestamp(), Pid}. + +incremented({Size, Timestamp, Pid}) -> + {Size + 1, Timestamp, Pid}. -record_request_for_connection(Tid, Pid) -> - catch ets:update_counter(Tid, Pid, {2, 1, ?PIPELINE_MAX, ?PIPELINE_MAX}). +decremented({Size, _Timestamp, Pid}) -> + {Size - 1, os:timestamp(), Pid}. for_each_connection_pid(Tid, Fun) -> catch ets:foldl(fun({Pid, _}, _) -> Fun(Pid) end, undefined, Tid), diff --git a/test/ibrowse_functional_tests.erl b/test/ibrowse_functional_tests.erl index 0a68e14..fc7afec 100644 --- a/test/ibrowse_functional_tests.erl +++ b/test/ibrowse_functional_tests.erl @@ -56,15 +56,10 @@ balanced_connections() -> timer:sleep(1000), - Diffs = [Count - BalancedNumberOfRequestsPerConnection || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()], - ?assertEqual(MaxSessions, length(Diffs)), + Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()], + ?assertEqual(MaxSessions, length(Counts)), - lists:foreach(fun(X) -> ?assertEqual(yep, close_to_zero(X)) end, Diffs). - -close_to_zero(0) -> yep; -close_to_zero(-1) -> yep; -close_to_zero(1) -> yep; -close_to_zero(X) -> {nope, X}. + ?assertEqual(lists:duplicate(MaxSessions, BalancedNumberOfRequestsPerConnection), Counts). times(0, _) -> ok; From 78d1814638bcf2b2d7c7441a418e3f52d31f58d5 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Thu, 20 Nov 2014 16:49:28 +0000 Subject: [PATCH 33/45] Filled in more functional tests Added additional functional tests ensuring that the pipelines empty and added some robustness around the test server. --- test/ibrowse_functional_tests.erl | 72 +++++++++++++++++++++++++++++-- test/ibrowse_test_server.erl | 27 +++++++----- 2 files changed, 86 insertions(+), 13 deletions(-) diff --git a/test/ibrowse_functional_tests.erl b/test/ibrowse_functional_tests.erl index fc7afec..0e43f6a 100644 --- a/test/ibrowse_functional_tests.erl +++ b/test/ibrowse_functional_tests.erl @@ -12,6 +12,9 @@ -define(SERVER_PORT, 8181). -define(BASE_URL, "http://localhost:" ++ integer_to_list(?SERVER_PORT)). +-define(SHORT_TIMEOUT_MS, 5000). +-define(LONG_TIMEOUT_MS, 30000). +-define(PAUSE_FOR_CONNECTIONS_MS, 2000). setup() -> application:start(crypto), @@ -33,7 +36,10 @@ running_server_fixture_test_() -> [ ?TIMEDTEST("Simple request can be honored", simple_request), ?TIMEDTEST("Slow server causes timeout", slow_server_timeout), - ?TIMEDTEST("Requests are balanced over connections", balanced_connections) + ?TIMEDTEST("Pipeline depth goes down with responses", pipeline_depth), + ?TIMEDTEST("Timeout closes pipe", closing_pipes), + ?TIMEDTEST("Requests are balanced over connections", balanced_connections), + ?TIMEDTEST("Pipeline too small signals retries", small_pipeline) ] }. @@ -43,6 +49,44 @@ simple_request() -> slow_server_timeout() -> ?assertMatch({error, req_timedout}, ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [], 5000)). +pipeline_depth() -> + MaxSessions = 2, + MaxPipeline = 2, + RequestsSent = 2, + EmptyPipelineDepth = 0, + + ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()), + + Fun = fun() -> ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end, + times(RequestsSent, fun() -> spawn_link(Fun) end), + + timer:sleep(?PAUSE_FOR_CONNECTIONS_MS), + + Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()], + ?assertEqual(MaxSessions, length(Counts)), + ?assertEqual(lists:duplicate(MaxSessions, EmptyPipelineDepth), Counts). + +closing_pipes() -> + MaxSessions = 2, + MaxPipeline = 2, + RequestsSent = 2, + BalancedNumberOfRequestsPerConnection = 1, + + ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()), + + Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end, + times(RequestsSent, fun() -> spawn_link(Fun) end), + + timer:sleep(?PAUSE_FOR_CONNECTIONS_MS), + + Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()], + ?assertEqual(MaxSessions, length(Counts)), + ?assertEqual(lists:duplicate(MaxSessions, BalancedNumberOfRequestsPerConnection), Counts), + + timer:sleep(?SHORT_TIMEOUT_MS), + + ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()). + balanced_connections() -> MaxSessions = 4, MaxPipeline = 100, @@ -51,16 +95,38 @@ balanced_connections() -> ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()), - Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], 30000) end, + Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?LONG_TIMEOUT_MS) end, times(RequestsSent, fun() -> spawn_link(Fun) end), - timer:sleep(1000), + timer:sleep(?PAUSE_FOR_CONNECTIONS_MS), Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()], ?assertEqual(MaxSessions, length(Counts)), ?assertEqual(lists:duplicate(MaxSessions, BalancedNumberOfRequestsPerConnection), Counts). +small_pipeline() -> + MaxSessions = 10, + MaxPipeline = 10, + RequestsSent = 100, + FullRequestsPerConnection = 10, + + ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()), + + Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end, + times(RequestsSent, fun() -> spawn(Fun) end), + + timer:sleep(?PAUSE_FOR_CONNECTIONS_MS), %% Wait for everyone to get in line + + Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()], + ?assertEqual(MaxSessions, length(Counts)), + + ?assertEqual(lists:duplicate(MaxSessions, FullRequestsPerConnection), Counts), + + Response = ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS), + + ?assertEqual({error, retry_later}, Response). + times(0, _) -> ok; times(X, Fun) -> diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index 940552a..d12d1c4 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -68,11 +68,7 @@ do_accept(ssl, Listen_sock) -> accept_loop(Sock, Sock_type) -> case do_accept(Sock_type, Sock) of {ok, Conn} -> - Pid = spawn_link( - fun() -> - server_loop(Conn, Sock_type, #request{}) - end), - ets:insert(?CONN_PIPELINE_DEPTH, {Pid, 0}), + Pid = spawn_link(fun() -> connection(Conn, Sock_type) end), set_controlling_process(Conn, Sock_type, Pid), Pid ! {setopts, [{active, true}]}, accept_loop(Sock, Sock_type); @@ -87,6 +83,14 @@ accept_loop(Sock, Sock_type) -> Err end. +connection(Conn, Sock_type) -> + ets:insert(?CONN_PIPELINE_DEPTH, {self(), 0}), + try + server_loop(Conn, Sock_type, #request{}) + after + ets:delete(?CONN_PIPELINE_DEPTH, self()) + end. + set_controlling_process(Sock, tcp, Pid) -> gen_tcp:controlling_process(Sock, Pid); set_controlling_process(Sock, ssl, Pid) -> @@ -100,14 +104,19 @@ setopts(Sock, ssl, Opts) -> server_loop(Sock, Sock_type, #request{headers = Headers} = Req) -> receive {http, Sock, {http_request, HttpMethod, HttpUri, HttpVersion}} -> + ets:update_counter(?CONN_PIPELINE_DEPTH, self(), 1), server_loop(Sock, Sock_type, Req#request{method = HttpMethod, uri = HttpUri, version = HttpVersion}); {http, Sock, {http_header, _, _, _, _} = H} -> server_loop(Sock, Sock_type, Req#request{headers = [H | Headers]}); {http, Sock, http_eoh} -> - ets:update_counter(?CONN_PIPELINE_DEPTH, self(), 1), - process_request(Sock, Sock_type, Req), + case process_request(Sock, Sock_type, Req) of + not_done -> + ok; + _ -> + ets:update_counter(?CONN_PIPELINE_DEPTH, self(), -1) + end, server_loop(Sock, Sock_type, #request{}); {http, Sock, {http_error, Err}} -> do_trace("Error parsing HTTP request:~n" @@ -172,7 +181,6 @@ process_request(Sock, Sock_type, uri = {abs_path, "/ibrowse_head_transfer_enc"}}) -> Resp = <<"HTTP/1.1 400 Bad Request\r\nServer: Apache-Coyote/1.1\r\nContent-Length:5\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\n\r\nabcde">>, do_send(Sock, Sock_type, Resp); - process_request(Sock, Sock_type, #request{method='GET', headers = Headers, @@ -210,7 +218,7 @@ process_request(Sock, Sock_type, Resp = <<"HTTP/1.1 303 See Other\r\nLocation: http://example.org\r\nContent-Length: 5\r\n\r\nabcde">>, do_send(Sock, Sock_type, Resp); process_request(_Sock, _Sock_type, #request{uri = {abs_path, "/never_respond"} } ) -> - noop; + not_done; process_request(Sock, Sock_type, Req) -> do_trace("Recvd req: ~p~n", [Req]), Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>, @@ -221,7 +229,6 @@ do_send(Sock, tcp, Resp) -> do_send(Sock, ssl, Resp) -> ssl:send(Sock, Resp). - %%------------------------------------------------------------------------------ %% Utility functions %%------------------------------------------------------------------------------ From f1244abe2c0e9e5646473af6db30b3b99031f882 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Wed, 13 Aug 2014 11:22:55 +0100 Subject: [PATCH 34/45] Changed travis-ci build status link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b68f197..5820447 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ibrowse [![Build Status](https://secure.travis-ci.org/johannesh/ibrowse.png)](http://travis-ci.org/johannesh/ibrowse) +# ibrowse [![Build Status](https://secure.travis-ci.org/cmullaparthi/ibrowse.png)](http://travis-ci.org/cmullaparthi/ibrowse) ibrowse is a HTTP client written in erlang. From e4d41ec4535fc32081d286d143b15b15185e1692 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Thu, 20 Nov 2014 17:02:29 +0000 Subject: [PATCH 35/45] Whitespace cleanup --- test/ibrowse_test.erl | 72 +++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/test/ibrowse_test.erl b/test/ibrowse_test.erl index 407ffcb..4ddb9c1 100644 --- a/test/ibrowse_test.erl +++ b/test/ibrowse_test.erl @@ -208,42 +208,42 @@ dump_errors(Key, Iod) -> %% Unit Tests %%------------------------------------------------------------------------------ -define(TEST_LIST, [{"http://intranet/messenger", get}, - {"http://www.google.co.uk", get}, - {"http://www.google.com", get}, - {"http://www.google.com", options}, - {"https://mail.google.com", get}, - {"http://www.sun.com", get}, - {"http://www.oracle.com", get}, - {"http://www.bbc.co.uk", get}, - {"http://www.bbc.co.uk", trace}, - {"http://www.bbc.co.uk", options}, - {"http://yaws.hyber.org", get}, - {"http://jigsaw.w3.org/HTTP/ChunkedScript", get}, - {"http://jigsaw.w3.org/HTTP/TE/foo.txt", get}, - {"http://jigsaw.w3.org/HTTP/TE/bar.txt", get}, - {"http://jigsaw.w3.org/HTTP/connection.html", get}, - {"http://jigsaw.w3.org/HTTP/cc.html", get}, - {"http://jigsaw.w3.org/HTTP/cc-private.html", get}, - {"http://jigsaw.w3.org/HTTP/cc-proxy-revalidate.html", get}, - {"http://jigsaw.w3.org/HTTP/cc-nocache.html", get}, - {"http://jigsaw.w3.org/HTTP/h-content-md5.html", get}, - {"http://jigsaw.w3.org/HTTP/h-retry-after.html", get}, - {"http://jigsaw.w3.org/HTTP/h-retry-after-date.html", get}, - {"http://jigsaw.w3.org/HTTP/neg", get}, - {"http://jigsaw.w3.org/HTTP/negbad", get}, - {"http://jigsaw.w3.org/HTTP/400/toolong/", get}, - {"http://jigsaw.w3.org/HTTP/300/", get}, - {"http://jigsaw.w3.org/HTTP/Basic/", get, [{basic_auth, {"guest", "guest"}}]}, - {"http://jigsaw.w3.org/HTTP/CL/", get}, - {"http://www.httpwatch.com/httpgallery/chunked/", get}, - {"https://github.com", get, [{ssl_options, [{depth, 2}]}]}, - {local_test_fun, test_20122010, []}, - {local_test_fun, test_pipeline_head_timeout, []}, - {local_test_fun, test_head_transfer_encoding, []}, - {local_test_fun, test_head_response_with_body, []}, - {local_test_fun, test_303_response_with_a_body, []}, - {local_test_fun, test_binary_headers, []} - ]). + {"http://www.google.co.uk", get}, + {"http://www.google.com", get}, + {"http://www.google.com", options}, + {"https://mail.google.com", get}, + {"http://www.sun.com", get}, + {"http://www.oracle.com", get}, + {"http://www.bbc.co.uk", get}, + {"http://www.bbc.co.uk", trace}, + {"http://www.bbc.co.uk", options}, + {"http://yaws.hyber.org", get}, + {"http://jigsaw.w3.org/HTTP/ChunkedScript", get}, + {"http://jigsaw.w3.org/HTTP/TE/foo.txt", get}, + {"http://jigsaw.w3.org/HTTP/TE/bar.txt", get}, + {"http://jigsaw.w3.org/HTTP/connection.html", get}, + {"http://jigsaw.w3.org/HTTP/cc.html", get}, + {"http://jigsaw.w3.org/HTTP/cc-private.html", get}, + {"http://jigsaw.w3.org/HTTP/cc-proxy-revalidate.html", get}, + {"http://jigsaw.w3.org/HTTP/cc-nocache.html", get}, + {"http://jigsaw.w3.org/HTTP/h-content-md5.html", get}, + {"http://jigsaw.w3.org/HTTP/h-retry-after.html", get}, + {"http://jigsaw.w3.org/HTTP/h-retry-after-date.html", get}, + {"http://jigsaw.w3.org/HTTP/neg", get}, + {"http://jigsaw.w3.org/HTTP/negbad", get}, + {"http://jigsaw.w3.org/HTTP/400/toolong/", get}, + {"http://jigsaw.w3.org/HTTP/300/", get}, + {"http://jigsaw.w3.org/HTTP/Basic/", get, [{basic_auth, {"guest", "guest"}}]}, + {"http://jigsaw.w3.org/HTTP/CL/", get}, + {"http://www.httpwatch.com/httpgallery/chunked/", get}, + {"https://github.com", get, [{ssl_options, [{depth, 2}]}]}, + {local_test_fun, test_20122010, []}, + {local_test_fun, test_pipeline_head_timeout, []}, + {local_test_fun, test_head_transfer_encoding, []}, + {local_test_fun, test_head_response_with_body, []}, + {local_test_fun, test_303_response_with_a_body, []}, + {local_test_fun, test_binary_headers, []} + ]). unit_tests() -> unit_tests([]). From 642c082d54777857cb8d6185ab123666e7a4520e Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Thu, 20 Nov 2014 17:44:05 +0000 Subject: [PATCH 36/45] Fixed matchspec miss and added retry logic w/ ets In prior commit, missed switch of matchspec to work with deletes, fixed here. Added retry logic for race conditions around lb and conn both trying to update same record at the same time. If more than max is experienced, just let it go, things will probably be ok. --- src/ibrowse_lb.erl | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 3d487d4..eabba3a 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -38,8 +38,9 @@ proc_state}). -define(PIPELINE_MAX, 99999). +-define(MAX_RETRIES, 3). -define(KEY_MATCHSPEC_BY_PID(Pid), [{{{'_', '_', Pid}, '_'}, [], ['$_']}]). --define(KEY_MATCHSPEC(Key), [{{Key, '_'}, [], ['$_']}]). +-define(KEY_MATCHSPEC_BY_PID_FOR_DELETE(Pid), [{{{'_', '_', Pid}, '_'}, [], [true]}]). -define(KEY_MATCHSPEC_FOR_DELETE(Key), [{{Key, '_'}, [], [true]}]). -include("ibrowse.hrl"). @@ -77,22 +78,10 @@ stop(Lb_pid) -> report_connection_down(Tid) -> %% Don't cascade errors since Tid is really managed by other process - catch ets:select_delete(Tid, ?KEY_MATCHSPEC_BY_PID(self())). + catch ets:select_delete(Tid, ?KEY_MATCHSPEC_BY_PID_FOR_DELETE(self())). report_request_complete(Tid) -> - %% Don't cascade errors since Tid is really managed by other process - catch case ets:select(Tid, ?KEY_MATCHSPEC_BY_PID(self())) of - [MatchKey] -> - case ets:select_delete(Tid, ?KEY_MATCHSPEC_FOR_DELETE(MatchKey)) of - 1 -> - ets:insert(Tid, {decremented(MatchKey), undefined}), - true; - _ -> - false - end; - _ -> - false - end. + report_request_complete(Tid, ?MAX_RETRIES). %%==================================================================== %% Server functions @@ -223,13 +212,18 @@ code_change(_OldVsn, State, _Extra) -> %%% Internal functions %%-------------------------------------------------------------------- find_best_connection(Tid, Max_pipeline_size) -> + find_best_connection(Tid, Max_pipeline_size, ?MAX_RETRIES). + +find_best_connection(_Tid, _Max_pipeline_size, 0) -> + {error, retry_later}; +find_best_connection(Tid, Max_pipeline_size, RemainingRetries) -> case ets:first(Tid) of {Size, _Timestamp, Pid} = Key when Size < Max_pipeline_size -> case record_request_for_connection(Tid, Key) of true -> {ok, Pid}; false -> - find_best_connection(Tid, Max_pipeline_size) + find_best_connection(Tid, Max_pipeline_size, RemainingRetries - 1) end; _ -> {error, retry_later} @@ -257,6 +251,24 @@ record_request_for_connection(Tid, Key) -> false end. +report_request_complete(_Tid, 0) -> + false; +report_request_complete(Tid, RemainingRetries) -> + %% Don't cascade errors since Tid is really managed by other process + catch case ets:select(Tid, ?KEY_MATCHSPEC_BY_PID(self())) of + [MatchKey] -> + case ets:select_delete(Tid, ?KEY_MATCHSPEC_FOR_DELETE(MatchKey)) of + 1 -> + ets:insert(Tid, {decremented(MatchKey), undefined}), + true; + _ -> + report_request_complete(Tid, RemainingRetries - 1) + end; + _ -> + false + end. + + new_key(Pid) -> {1, os:timestamp(), Pid}. From 4b0fb6b8a0e80c5afe16601a82245776ea2cf498 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Thu, 20 Nov 2014 18:01:25 +0000 Subject: [PATCH 37/45] Removed duplicate documentation --- src/ibrowse.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ibrowse.erl b/src/ibrowse.erl index 0ade277..951cfe1 100644 --- a/src/ibrowse.erl +++ b/src/ibrowse.erl @@ -158,7 +158,7 @@ stop() -> %% respHeader() = {headerName(), headerValue()} %% headerName() = string() %% headerValue() = string() -%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason} +%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason} %% req_id() = term() %% ResponseBody = string() | {file, Filename} %% Reason = term() From 5b8993e10ba76bfd76cd7b992dfcf33cec4de938 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Thu, 20 Nov 2014 18:01:51 +0000 Subject: [PATCH 38/45] Added basic test for show_dest_status --- test/ibrowse_functional_tests.erl | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/ibrowse_functional_tests.erl b/test/ibrowse_functional_tests.erl index 0e43f6a..8df662b 100644 --- a/test/ibrowse_functional_tests.erl +++ b/test/ibrowse_functional_tests.erl @@ -39,7 +39,8 @@ running_server_fixture_test_() -> ?TIMEDTEST("Pipeline depth goes down with responses", pipeline_depth), ?TIMEDTEST("Timeout closes pipe", closing_pipes), ?TIMEDTEST("Requests are balanced over connections", balanced_connections), - ?TIMEDTEST("Pipeline too small signals retries", small_pipeline) + ?TIMEDTEST("Pipeline too small signals retries", small_pipeline), + ?TIMEDTEST("Dest status can be gathered", status) ] }. @@ -127,6 +128,20 @@ small_pipeline() -> ?assertEqual({error, retry_later}, Response). +status() -> + MaxSessions = 10, + MaxPipeline = 10, + RequestsSent = 100, + + Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end, + times(RequestsSent, fun() -> spawn(Fun) end), + + timer:sleep(?PAUSE_FOR_CONNECTIONS_MS), %% Wait for everyone to get in line + + ibrowse:show_dest_status(), + ibrowse:show_dest_status("http://localhost:8181"). + + times(0, _) -> ok; times(X, Fun) -> From 86ccc45f9667c1f310ef1ab4ba902b50b96ac999 Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Thu, 20 Nov 2014 18:27:32 +0000 Subject: [PATCH 39/45] Updated contributes/authors --- CONTRIBUTORS | 3 +++ src/ibrowse_http_client.erl | 3 +++ src/ibrowse_lb.erl | 3 +++ test/ibrowse_functional_tests.erl | 5 +++-- test/ibrowse_test_server.erl | 5 +++-- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 21e8d06..665e64b 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -9,9 +9,12 @@ In alphabetical order: Adam Kocoloski Andrew Tunnell-Jones Anthony Molinaro +Benjamin P Lee (https://github.com/benjaminplee) Benoit Chesneau (https://github.com/benoitc) +Brian Richards (http://github.com/richbria) Chris Newcombe Dan Kelley +Dan Schwabe (https://github.com/dfschwabe) Derek Upham Eric Merritt Erik Reitsma diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index c9161b0..d92db42 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -1,6 +1,9 @@ %%%------------------------------------------------------------------- %%% File : ibrowse_http_client.erl %%% Author : Chandrashekhar Mullaparthi +%%% Benjamin Lee +%%% Dan Schwabe +%%% Brian Richards %%% Description : The name says it all %%% %%% Created : 11 Oct 2003 by Chandrashekhar Mullaparthi diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index eabba3a..656adda 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -1,6 +1,9 @@ %%%------------------------------------------------------------------- %%% File : ibrowse_lb.erl %%% Author : chandru +%%% Benjamin Lee +%%% Dan Schwabe +%%% Brian Richards %%% Description : %%% %%% Created : 6 Mar 2008 by chandru diff --git a/test/ibrowse_functional_tests.erl b/test/ibrowse_functional_tests.erl index 8df662b..8b75e3d 100644 --- a/test/ibrowse_functional_tests.erl +++ b/test/ibrowse_functional_tests.erl @@ -1,6 +1,7 @@ %%% File : ibrowse_functional_tests.erl -%%% Authors : Benjamin Lee -%%% Brian Richards +%%% Authors : Benjamin Lee +%%% Dan Schwabe +%%% Brian Richards %%% Description : Functional tests of the ibrowse library using a live test HTTP server %%% Created : 18 November 2014 by Benjamin Lee diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index d12d1c4..1f5202f 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -1,7 +1,8 @@ %%% File : ibrowse_test_server.erl %%% Author : Chandrashekhar Mullaparthi -%%% Benjamin Lee -%%% Brian Richards +%%% Benjamin Lee +%%% Dan Schwabe +%%% Brian Richards %%% Description : A server to simulate various test scenarios %%% Created : 17 Oct 2010 by Chandrashekhar Mullaparthi From 247dd563fbf5b11a54bfefe96b6cd9bc2efa5a1b Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Fri, 21 Nov 2014 04:33:08 +0000 Subject: [PATCH 40/45] Fixed bug with connection req completion Algorithm change had bug where ets:select return value was incorrectly assumed to be the object key and not the entire object causing the following delete attempt based on a matchspec and the key to fail. This meant that ets was not updated to reflect the completed requests on each connection and causing exaustion of pipelines event though connections were idle. Functional test added which demonstrated the problem. --- src/ibrowse_lb.erl | 2 +- test/ibrowse_functional_tests.erl | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 656adda..656c3f9 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -259,7 +259,7 @@ report_request_complete(_Tid, 0) -> report_request_complete(Tid, RemainingRetries) -> %% Don't cascade errors since Tid is really managed by other process catch case ets:select(Tid, ?KEY_MATCHSPEC_BY_PID(self())) of - [MatchKey] -> + [{MatchKey, _}] -> case ets:select_delete(Tid, ?KEY_MATCHSPEC_FOR_DELETE(MatchKey)) of 1 -> ets:insert(Tid, {decremented(MatchKey), undefined}), diff --git a/test/ibrowse_functional_tests.erl b/test/ibrowse_functional_tests.erl index 8b75e3d..e55c5b2 100644 --- a/test/ibrowse_functional_tests.erl +++ b/test/ibrowse_functional_tests.erl @@ -38,6 +38,7 @@ running_server_fixture_test_() -> ?TIMEDTEST("Simple request can be honored", simple_request), ?TIMEDTEST("Slow server causes timeout", slow_server_timeout), ?TIMEDTEST("Pipeline depth goes down with responses", pipeline_depth), + ?TIMEDTEST("Pipelines refill", pipeline_refill), ?TIMEDTEST("Timeout closes pipe", closing_pipes), ?TIMEDTEST("Requests are balanced over connections", balanced_connections), ?TIMEDTEST("Pipeline too small signals retries", small_pipeline), @@ -68,6 +69,26 @@ pipeline_depth() -> ?assertEqual(MaxSessions, length(Counts)), ?assertEqual(lists:duplicate(MaxSessions, EmptyPipelineDepth), Counts). +pipeline_refill() -> + MaxSessions = 2, + MaxPipeline = 2, + RequestsToFill = MaxSessions * MaxPipeline, + + %% Send off enough requests to fill sessions and pipelines in rappid succession + Fun = fun() -> ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end, + times(RequestsToFill, fun() -> spawn_link(Fun) end), + timer:sleep(?PAUSE_FOR_CONNECTIONS_MS), + + % Verify that connections properly reported their completed responses and can still accept more + ?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS)), + + % and do it again to make sure we really are clear + times(RequestsToFill, fun() -> spawn_link(Fun) end), + timer:sleep(?PAUSE_FOR_CONNECTIONS_MS), + + % Verify that connections properly reported their completed responses and can still accept more + ?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS)). + closing_pipes() -> MaxSessions = 2, MaxPipeline = 2, From 1a680f00e0c053cb81424e200f6657cdd0ad71ca Mon Sep 17 00:00:00 2001 From: benjaminplee Date: Fri, 21 Nov 2014 14:38:12 +0000 Subject: [PATCH 41/45] Made test server less chatty while shutting down Connections warned of badargs to ets:delete when shutting the server down with open connections; created noise while reviewing test results. --- test/ibrowse_test_server.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index 1f5202f..dc0d7e2 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -85,11 +85,11 @@ accept_loop(Sock, Sock_type) -> end. connection(Conn, Sock_type) -> - ets:insert(?CONN_PIPELINE_DEPTH, {self(), 0}), + catch ets:insert(?CONN_PIPELINE_DEPTH, {self(), 0}), try server_loop(Conn, Sock_type, #request{}) after - ets:delete(?CONN_PIPELINE_DEPTH, self()) + catch ets:delete(?CONN_PIPELINE_DEPTH, self()) end. set_controlling_process(Sock, tcp, Pid) -> From 37fce828469d3a0ca6aae95b7a8b30deb9187d4d Mon Sep 17 00:00:00 2001 From: "benjamin.lee" Date: Thu, 11 Dec 2014 21:35:12 +0000 Subject: [PATCH 42/45] Fixed extraction of Pid for iteration with new key Missed in original work. --- src/ibrowse_lb.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ibrowse_lb.erl b/src/ibrowse_lb.erl index 656c3f9..794ba45 100644 --- a/src/ibrowse_lb.erl +++ b/src/ibrowse_lb.erl @@ -282,5 +282,5 @@ decremented({Size, _Timestamp, Pid}) -> {Size - 1, os:timestamp(), Pid}. for_each_connection_pid(Tid, Fun) -> - catch ets:foldl(fun({Pid, _}, _) -> Fun(Pid) end, undefined, Tid), + ets:foldl(fun({{_, _, Pid}, _}, _) -> Fun(Pid) end, undefined, Tid), ok. From 3a4044d4c185010f35c000bd94a62b23b77a91f0 Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Mon, 28 Sep 2015 08:27:35 +0100 Subject: [PATCH 43/45] Compile test modules --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 28dfda8..d2e61c6 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ eunit_test: all ./rebar eunit test: all + cd test; erl -pa ../../ibrowse/ebin -make; cd ../; \ erl -noshell -pa test -pa ebin -s ibrowse_test unit_tests \ -s ibrowse_test verify_chunked_streaming \ -s ibrowse_test test_chunked_streaming_once \ From b4ad6df3c54a6e2a88a63e3ac97090baf04919cf Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Mon, 28 Sep 2015 20:23:43 +0100 Subject: [PATCH 44/45] Workaround travis-ci build failure. --- test/ibrowse_test.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/ibrowse_test.erl b/test/ibrowse_test.erl index 0787493..4bf8ffc 100644 --- a/test/ibrowse_test.erl +++ b/test/ibrowse_test.erl @@ -266,8 +266,9 @@ unit_tests() -> unit_tests(Options, Test_list) -> application:start(crypto), + application:start(asn1), application:start(public_key), - application:ensure_all_started(ssl), + application:start(ssl), (catch ibrowse_test_server:start_server(8181, tcp)), application:start(ibrowse), Options_1 = Options ++ [{connect_timeout, 5000}], From 3c96a918ca109288c91a4c8277d0ab7b2ff2d5dc Mon Sep 17 00:00:00 2001 From: Chandrashekhar Mullaparthi Date: Mon, 28 Sep 2015 20:43:31 +0100 Subject: [PATCH 45/45] Removed update to author list. Names are already in the CONTRIBUTORS file. This is a consistent approach to the way contributions from everyone else has been handled over the years. Correct authorship has been acknowledged in the new module ibrowse_functional_tests.erl --- test/ibrowse_test_server.erl | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index 7025286..90c46c4 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -1,8 +1,5 @@ %%% File : ibrowse_test_server.erl %%% Author : Chandrashekhar Mullaparthi -%%% Benjamin Lee -%%% Dan Schwabe -%%% Brian Richards %%% Description : A server to simulate various test scenarios %%% Created : 17 Oct 2010 by Chandrashekhar Mullaparthi