- %%%-------------------------------------------------------------------
- %%% File : ibrowse_lb.erl
- %%% Author : chandru <chandrashekhar.mullaparthi@t-mobile.co.uk>
- %%% Description :
- %%%
- %%% Created : 6 Mar 2008 by chandru <chandrashekhar.mullaparthi@t-mobile.co.uk>
- %%%-------------------------------------------------------------------
-
- -module(ibrowse_lb).
- -author(chandru).
- -behaviour(gen_server).
-
- %% External exports
- -export([
- start_link/1,
- spawn_connection/6,
- stop/1,
- report_connection_down/1,
- report_request_complete/1
- ]).
-
- %% gen_server callbacks
- -export([
- init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2,
- code_change/3
- ]).
-
- -record(state, {parent_pid,
- ets_tid,
- host,
- port,
- max_sessions,
- max_pipeline_size,
- proc_state}).
-
- -define(PIPELINE_MAX, 99999).
- -define(MAX_RETRIES, 3).
- -define(KEY_MATCHSPEC_BY_PID(Pid), [{{{'_', '_', Pid}, '_'}, [], ['$_']}]).
- -define(KEY_MATCHSPEC_BY_PID_FOR_DELETE(Pid), [{{{'_', '_', Pid}, '_'}, [], [true]}]).
- -define(KEY_MATCHSPEC_FOR_DELETE(Key), [{{Key, '_'}, [], [true]}]).
-
- -include("ibrowse.hrl").
-
- %%====================================================================
- %% External functions
- %%====================================================================
- %%--------------------------------------------------------------------
- %% Function: start_link/0
- %% Description: Starts the server
- %%--------------------------------------------------------------------
- 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.
-
- 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_FOR_DELETE(self())).
-
- report_request_complete(Tid) ->
- report_request_complete(Tid, ?MAX_RETRIES).
-
- %%====================================================================
- %% Server functions
- %%====================================================================
- %%--------------------------------------------------------------------
- %% Function: init/1
- %% Description: Initiates the server
- %% Returns: {ok, State} |
- %% {ok, State, Timeout} |
- %% ignore |
- %% {stop, Reason}
- %%--------------------------------------------------------------------
- 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}}.
-
- %%--------------------------------------------------------------------
- %% Function: handle_call/3
- %% Description: Handling call messages
- %% Returns: {reply, Reply, State} |
- %% {reply, Reply, State, Timeout} |
- %% {noreply, State} |
- %% {noreply, State, Timeout} |
- %% {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) ->
- for_each_connection_pid(Tid, fun(Pid) -> ibrowse_http_client:stop(Pid) end),
- gen_server:reply(_From, ok),
- {stop, normal, State};
-
- handle_call(_, _From, #state{proc_state = shutting_down} = State) ->
- {reply, {error, 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 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),
- record_new_connection(Tid, Pid),
- 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},
- {reply, Reply, State}.
-
- %%--------------------------------------------------------------------
- %% Function: handle_cast/2
- %% Description: Handling cast messages
- %% Returns: {noreply, State} |
- %% {noreply, State, Timeout} |
- %% {stop, Reason, State} (terminate/2 is called)
- %%--------------------------------------------------------------------
- handle_cast(_Msg, State) ->
- {noreply, State}.
-
- %%--------------------------------------------------------------------
- %% Function: handle_info/2
- %% Description: Handling all non call/cast messages
- %% Returns: {noreply, 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({trace, Bool}, #state{ets_tid = undefined} = State) ->
- put(my_trace_flag, Bool),
- {noreply, State};
- handle_info({trace, Bool}, #state{ets_tid = Tid} = State) ->
- for_each_connection_pid(Tid, fun(Pid) -> ibrowse_http_client:trace(Pid, Bool) end),
- put(my_trace_flag, Bool),
- {noreply, 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
- %% 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}};
-
- handle_info(shutdown, State) ->
- {stop, normal, State};
-
- handle_info(_Info, State) ->
- {noreply, State}.
-
- %%--------------------------------------------------------------------
- %% Function: terminate/2
- %% Description: Shutdown the server
- %% Returns: any (ignored by gen_server)
- %%--------------------------------------------------------------------
- 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(?LOAD_BALANCER_NAMED_TABLE, #lb_pid{host_port = {Host, Port}, pid = self()}),
- ok.
-
- %%--------------------------------------------------------------------
- %% Func: code_change/3
- %% Purpose: Convert process state when code is changed
- %% Returns: {ok, NewState}
- %%--------------------------------------------------------------------
- code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
- %%--------------------------------------------------------------------
- %%% 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, RemainingRetries - 1)
- end;
- _ ->
- {error, retry_later}
- end.
-
- maybe_create_ets(#state{ets_tid = undefined} = State) ->
- Tid = ets:new(?CONNECTIONS_LOCAL_TABLE, [public, ordered_set]),
- State#state{ets_tid = Tid};
- 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, {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.
-
- 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}.
-
- incremented({Size, Timestamp, Pid}) ->
- {Size + 1, Timestamp, Pid}.
-
- 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),
- ok.
|