Current section
Files
Jump to
Current section
Files
src/sp_client.erl
%% @doc This module implements Synchronous Pull (i.e. #'basic get') over AMQP.
%% It handles the reconnection and restart of failed child processes
%%
-module(sp_client).
-include_lib("amqp_client/include/amqp_client.hrl").
-behaviour(gen_server).
% External API
-export([pull/2,
get_queue_attributes/2,
purge/2]).
%% Lifetime API
-export([start_link/3]).
% Callback API
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3,
format_status/2]).
-record(state, {channel = undefined,
app_id = undefined}).
-define(RECONNECT_TIME, 5000).
-spec pull(atom() | pid(), binary()) -> {ok, binary()} | empty.
pull(PullClient, Queue) ->
gen_server:call(PullClient, {pull, Queue}).
-spec get_queue_attributes(atom(), binary()) -> {ok, integer(), integer()}.
get_queue_attributes(PullClient, Queue) ->
gen_server:call(PullClient, {get_queue_attributes, Queue}).
-spec purge(atom(), binary()) -> {ok, integer()}.
purge(PullClient, Queue) ->
gen_server:call(PullClient, {purge, Queue}).
-spec start_link(atom(), term(), pid()) -> {ok, pid()}.
start_link(Name, Configuration, ConnRef) ->
gen_server:start_link({local, Name}, ?MODULE, [Name, Configuration, ConnRef], []).
init([_Name, Configuration, ConnectionRef]) ->
timer:send_after(?RECONNECT_TIME, self(), {reconnect, Configuration, ConnectionRef}),
{ok, #state{channel = undefined}}.
handle_call({pull, Queue}, _From, State) ->
Get = #'basic.get'{queue = Queue, no_ack = true},
Reply = case amqp_channel:call(State#state.channel, Get) of
{#'basic.get_ok'{}, #amqp_msg{payload = Payload}} -> {ok, Payload};
#'basic.get_empty'{} -> empty
end,
{reply, Reply, State};
handle_call({get_queue_attributes, Queue}, _From, State) ->
Declare = #'queue.declare'{queue = Queue, passive = true},
#'queue.declare_ok'{message_count = MCount, consumer_count = CCount} = amqp_channel:call(State#state.channel, Declare),
{reply, {ok, MCount, CCount}, State};
handle_call({purge, Queue}, _From, State) ->
Purge = #'queue.purge'{queue = Queue},
#'queue.purge_ok'{message_count = Count} = amqp_channel:call(State#state.channel, Purge),
{reply, {ok, Count}, State}.
handle_cast(_Request, State) -> {noreply, State}. % Not used
handle_info({reconnect, Configuration, ConnectionRef},
#state{channel = undefined}) ->
{noreply, try_connect(Configuration, ConnectionRef)}.
terminate(_Reason, #state{channel = undefined}) -> ok;
terminate(_Reason, #state{channel = Channel}) ->
catch(amqp_channel:close(Channel)),
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}. % Code swapping not used
format_status(_Opt, [_PDict, State]) -> {?MODULE, State}. % For debugging
% Internal
try_connect(Configuration, ConnectionRef) ->
case amqp_connection_mgr:fetch(ConnectionRef) of
{ok, Connection} ->
{ok, Channel} = amqp_connection:open_channel(Connection, {amqp_direct_consumer, [self()]}),
amqp_definitions:inject(Channel, proplists:get_value(queue_definitions, Configuration, [])),
AppId = proplists:get_value(app_id, Configuration, list_to_binary(atom_to_list(node()))),
#state{channel = Channel, app_id = AppId};
{error, econnrefused} ->
timer:send_after(?RECONNECT_TIME, self(), {reconnect, Configuration, ConnectionRef}),
#state{channel = undefined}
end.