220 lines
7.7 KiB
Erlang
220 lines
7.7 KiB
Erlang
%%% ----------------------------------------------------------------------------
|
|
%%% @copyright (C) 2013, Erlang Solutions Ltd
|
|
%%% @author Diana Parra Corbacho <diana.corbacho@erlang-solutions.com>
|
|
%%% @doc Fusco Client Pool
|
|
%%%
|
|
%%% Pool of clients connected to the same server. Clients do not share state
|
|
%%% Recommended for BOSH where connections do not share cookies or any other
|
|
%%% headers state
|
|
%%%
|
|
%%% @end
|
|
%%%-----------------------------------------------------------------------------
|
|
-module(fusco_cp).
|
|
|
|
-behaviour(gen_server).
|
|
|
|
%% API
|
|
-export([start/3,
|
|
start_link/3,
|
|
get_client/1,
|
|
free_client/2,
|
|
stop/1
|
|
]).
|
|
|
|
-export([request/7]).
|
|
|
|
%% gen_server callbacks
|
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
|
terminate/2, code_change/3]).
|
|
|
|
-define(SERVER, ?MODULE).
|
|
|
|
-record(state, {destination,
|
|
options,
|
|
max,
|
|
total,
|
|
free,
|
|
busy,
|
|
queue
|
|
}).
|
|
|
|
%%%===================================================================
|
|
%%% API
|
|
%%%===================================================================
|
|
start(Destination, Options, MaxClients) ->
|
|
verify_max(MaxClients),
|
|
gen_server:start(?MODULE, [Destination, Options, MaxClients], []).
|
|
|
|
start_link(Destination, Options, MaxClients) ->
|
|
verify_max(MaxClients),
|
|
gen_server:start_link(?MODULE, [Destination, Options, MaxClients], []).
|
|
|
|
get_client(Pool) ->
|
|
try
|
|
gen_server:call(Pool, get_client)
|
|
catch
|
|
exit:{timeout, _} ->
|
|
{error, timeout}
|
|
end.
|
|
|
|
free_client(Pool, Client) ->
|
|
gen_server:cast(Pool, {free_client, Client}).
|
|
|
|
stop(Pool) ->
|
|
gen_server:cast(Pool, stop).
|
|
|
|
request(Pool, Path, Method, Hdrs, Body, SendRetry, Timeout) ->
|
|
case get_client(Pool) of
|
|
{error, _} = Error ->
|
|
Error;
|
|
Client ->
|
|
Reply = fusco:request(Client, Path, Method, Hdrs, Body, SendRetry,
|
|
Timeout),
|
|
free_client(Pool, Client),
|
|
Reply
|
|
end.
|
|
%%%===================================================================
|
|
%%% gen_server callbacks
|
|
%%%===================================================================
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% @private
|
|
%% @doc
|
|
%% Initializes the server
|
|
%%
|
|
%% @spec init(Args) -> {ok, State} |
|
|
%% {ok, State, Timeout} |
|
|
%% ignore |
|
|
%% {stop, Reason}
|
|
%% @end
|
|
%%--------------------------------------------------------------------
|
|
init([Destination, Options, MaxClients]) ->
|
|
process_flag(trap_exit, true),
|
|
{ok, #state{destination = Destination,
|
|
options = Options,
|
|
total = 0,
|
|
max = MaxClients,
|
|
free = [],
|
|
busy = [],
|
|
queue = queue:new()
|
|
}, 0}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% @private
|
|
%% @doc
|
|
%% Handling call messages
|
|
%%
|
|
%% @spec handle_call(Request, From, State) ->
|
|
%% {reply, Reply, State} |
|
|
%% {reply, Reply, State, Timeout} |
|
|
%% {noreply, State} |
|
|
%% {noreply, State, Timeout} |
|
|
%% {stop, Reason, Reply, State} |
|
|
%% {stop, Reason, State}
|
|
%% @end
|
|
%%--------------------------------------------------------------------
|
|
handle_call(get_client, _From, State = #state{free = [Client | Free],
|
|
busy = Busy}) ->
|
|
{reply, Client, State#state{free = Free,
|
|
busy = [Client | Busy]}};
|
|
handle_call(get_client, _From, State = #state{destination = Destination,
|
|
options = Options,
|
|
free = [],
|
|
max = M,
|
|
total = T,
|
|
busy = Busy})
|
|
when M > T ->
|
|
{ok, Pid} = fusco:start_link(Destination, Options),
|
|
{reply, Pid, State#state{total = T + 1,
|
|
busy = [Pid | Busy]}};
|
|
handle_call(get_client, From, State = #state{free = [],
|
|
max = M,
|
|
total = T,
|
|
queue = Queue})
|
|
when M == T ->
|
|
{noreply, State#state{queue = queue:in(From, Queue)}}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% @private
|
|
%% @doc
|
|
%% Handling cast messages
|
|
%%
|
|
%% @spec handle_cast(Msg, State) -> {noreply, State} |
|
|
%% {noreply, State, Timeout} |
|
|
%% {stop, Reason, State}
|
|
%% @end
|
|
%%--------------------------------------------------------------------
|
|
handle_cast({free_client, Pid}, State = #state{free = Free,
|
|
busy = Busy,
|
|
queue = Queue}) ->
|
|
case queue:is_empty(Queue) of
|
|
true ->
|
|
{noreply, State#state{free = [Pid | Free],
|
|
busy = lists:delete(Pid, Busy)}};
|
|
false ->
|
|
{{value, From}, Q2} = queue:out(Queue),
|
|
gen_server:reply(From, Pid),
|
|
{noreply, State#state{queue = Q2}}
|
|
end;
|
|
handle_cast(stop, State) ->
|
|
{stop, normal, State}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% @private
|
|
%% @doc
|
|
%% Handling all non call/cast messages
|
|
%%
|
|
%% @spec handle_info(Info, State) -> {noreply, State} |
|
|
%% {noreply, State, Timeout} |
|
|
%% {stop, Reason, State}
|
|
%% @end
|
|
%%--------------------------------------------------------------------
|
|
handle_info({'EXIT', From, _Reason}, State = #state{free = Free,
|
|
busy = Busy,
|
|
total = Total}) ->
|
|
{noreply, State#state{free = lists:delete(From, Free),
|
|
busy = lists:delete(From, Busy),
|
|
total = Total - 1}};
|
|
handle_info(timeout, #state{free = [], busy = [],
|
|
destination = Destination,
|
|
options = Options} = State) ->
|
|
{ok, Pid} = fusco:start_link(Destination, Options),
|
|
{noreply, State#state{free = [Pid], total = 1}};
|
|
handle_info(_Info, State) ->
|
|
{noreply, State}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% @private
|
|
%% @doc
|
|
%% This function is called by a gen_server when it is about to
|
|
%% terminate. It should be the opposite of Module:init/1 and do any
|
|
%% necessary cleaning up. When it returns, the gen_server terminates
|
|
%% with Reason. The return value is ignored.
|
|
%%
|
|
%% @spec terminate(Reason, State) -> void()
|
|
%% @end
|
|
%%--------------------------------------------------------------------
|
|
terminate(_Reason, #state{free = Free, busy = Busy}) ->
|
|
[fusco:disconnect(F) || F <- Free],
|
|
[fusco:disconnect(B) || B <- Busy],
|
|
ok.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% @private
|
|
%% @doc
|
|
%% Convert process state when code is changed
|
|
%%
|
|
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
|
|
%% @end
|
|
%%--------------------------------------------------------------------
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
{ok, State}.
|
|
|
|
%%%===================================================================
|
|
%%% Internal functions
|
|
%%%===================================================================
|
|
verify_max(Integer) when is_integer(Integer), Integer > 0 ->
|
|
ok;
|
|
verify_max(_) ->
|
|
throw(invalid_parameter).
|