Packages

RabbitMQ connection pool library

Current section

Files

Jump to
ex_rabbit_pool lib ex_rabbit_pool.ex
Raw

lib/ex_rabbit_pool.ex

defmodule ExRabbitPool do
alias ExRabbitPool.Worker.RabbitConnection, as: Conn
@type f :: ({:ok, AMQP.Channel.t()} | {:error, :disconnected | :out_of_channels} -> any())
@doc """
Gets a connection from a connection worker so any client can exec commands
manually
"""
@spec get_connection(atom()) :: {:ok, AMQP.Connection.t()} | {:error, :disconnected}
def get_connection(pool_id) do
:poolboy.transaction(pool_id, &Conn.get_connection/1)
end
@doc """
Retrieve a connection worker - we don't care about isolating access to each
worker - we use a pool purely in order to spread load (pool config strategy :fifo)
"""
@spec get_connection_worker(atom()) :: pid()
def get_connection_worker(pool_id) do
conn_worker = :poolboy.checkout(pool_id)
:ok = :poolboy.checkin(pool_id, conn_worker)
conn_worker
end
@doc """
Executes function f in the context of a channel, takes a connection worker
out of the pool, put that connection worker back into the pool so any
other concurrent client can have access to it, checks out a channel out of
the worker's channel pool, executes the function with the result of the
checkout and finally puts the channel back into the worker's pool.
"""
@spec with_channel(atom(), f()) :: any()
def with_channel(pool_id, fun) do
pool_id
|> get_connection_worker()
|> do_with_conn(fun)
end
@doc """
Gets a RabbitMQ channel out of a connection worker
"""
@spec checkout_channel(pid()) ::
{:ok, AMQP.Channel.t()} | {:error, :disconnected | :out_of_channels}
def checkout_channel(conn_worker) do
Conn.checkout_channel(conn_worker)
end
@doc """
Puts back a RabbitMQ channel into its corresponding connection worker
"""
@spec checkin_channel(pid(), AMQP.Channel.t()) :: :ok
def checkin_channel(conn_worker, channel) do
Conn.checkin_channel(conn_worker, channel)
end
# Gets a channel out of a connection worker and performs a function with it
# then it puts it back to the same connection worker, mimicking a transaction.
@spec do_with_conn(pid(), f()) :: any()
defp do_with_conn(conn_worker, fun) do
case checkout_channel(conn_worker) do
{:ok, channel} = ok_chan ->
try do
fun.(ok_chan)
after
:ok = checkin_channel(conn_worker, channel)
end
{:error, _} = error ->
fun.(error)
end
end
@spec create_queue_with_bind(
module(),
atom(),
AMQP.Basic.queue(),
AMQP.Basic.exchange(),
keyword()
) :: :ok | AMQP.Basic.error() | {:error, any()}
def create_queue_with_bind(adapter, pool_id, queue, exchange, options \\ []) do
queue_options = Keyword.get(options, :queue_options, [])
exchange_options = Keyword.get(options, :exchange_options, [])
bind_options = Keyword.get(options, :bind_options, [])
conn_worker = get_connection_worker(pool_id)
do_with_conn(conn_worker, fn
{:ok, channel} ->
with {:ok, _} <- adapter.declare_queue(channel, queue, queue_options),
:ok <- adapter.declare_exchange(channel, exchange, exchange_options),
:ok <- adapter.queue_bind(channel, queue, exchange, bind_options) do
:ok
else
{:error, _} = error -> error
end
{:error, _} = error ->
error
end)
end
end