Packages
step_flow
1.4.1
1.9.0-rc2
1.9.0-rc1
1.9.0-rc0
1.8.2
1.8.1
1.8.1-rc8
1.8.1-rc7
1.8.1-rc6
1.8.1-rc5
1.8.1-rc4
1.8.1-rc3
1.8.1-rc2
1.8.1-rc1
1.8.1-rc0
1.8.0
1.8.0-rc3
1.8.0-rc2
1.8.0-rc1
1.8.0-rc0
1.7.3
1.7.3-rc4
1.7.3-rc3
1.7.3-rc2
1.7.3-rc1
1.7.3-rc0
1.7.2
1.7.2-rc4
1.7.2-rc3
1.7.2-rc2
1.7.2-rc1
1.7.2-rc0
1.7.1
1.7.0
1.7.0-rc1
1.7.0-rc0
1.6.1
1.6.1-rc1
1.6.1-rc0
1.6.0
1.6.0-rc9
1.6.0-rc8
1.6.0-rc7
1.6.0-rc6
1.6.0-rc5
1.6.0-rc4
1.6.0-rc3
1.6.0-rc20
1.6.0-rc2
1.6.0-rc19
1.6.0-rc18
1.6.0-rc17
1.6.0-rc16
1.6.0-rc15
1.6.0-rc14
1.6.0-rc13
1.6.0-rc12
1.6.0-rc11
1.6.0-rc10
1.6.0-rc1
1.5.0
1.5.0-rc1
1.4.2-rc2
1.4.2-rc1
1.4.1
1.4.1-rc1
1.4.0
1.4.0-rc4
1.4.0-rc3
1.4.0-rc2
1.4.0-rc1
1.3.1
1.3.0
1.3.0-rc
1.2.0
1.1.0
1.0.0
1.0.0-rc9
1.0.0-rc8
1.0.0-rc7
1.0.0-rc6
1.0.0-rc5
1.0.0-rc1
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.18
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.11
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.4
0.0.3
0.0.2
0.0.1
Step flow manager for Elixir applications
Current section
Files
Jump to
Current section
Files
lib/step_flow/amqp/common_consumer.ex
defmodule StepFlow.Amqp.CommonConsumer do
@moduledoc """
Definition of a Common Consumer of RabbitMQ queue.
To implement a consumer,
```elixir
defmodule MyModule do
use StepFlow.Amqp.CommonConsumer, %{
queue: "name_of_the_rabbit_mq_queue",
exchange "name_of_exchange",
consumer: &MyModule.consume/4
}
def consume(channel, tag, redelivered, payload) do
...
Basic.ack(channel, tag)
end
end
```
"""
require Logger
alias StepFlow.Amqp.CommonConsumer
@doc false
defmacro __using__(opts) do
quote do
use GenServer
use AMQP
alias StepFlow.Amqp.CommonEmitter
alias StepFlow.Amqp.Helpers
def child_spec(_) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, []},
type: :worker
}
end
@doc false
def start_link do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
@impl true
@doc false
def init(:ok) do
url = Helpers.get_amqp_connection_url()
options = Helpers.get_amqp_connection_options()
queue = unquote(opts).queue
exchange_name = unquote(opts).exchange
prefetch_count = Map.get(unquote(opts), :prefetch_count)
CommonConsumer.rabbitmq_connect(url, options, queue, exchange_name, prefetch_count)
end
def disconnect do
GenServer.call(__MODULE__, :disconnect)
end
@impl true
def handle_call(:disconnect, _from, state) do
state = CommonConsumer.rabbitmq_disconnect(state)
{:reply, :ok, state}
end
@impl true
# Confirmation sent by broker after registering this process as consumer
def handle_info({:basic_consume_ok, %{consumer_tag: _consumer_tag}}, state) do
{:noreply, state}
end
@impl true
# Sent by broker when consumer is unexpectedly cancelled (such as after queue deletion)
def handle_info({:basic_cancel, %{consumer_tag: _consumer_tag}}, state) do
{:stop, :normal, state}
end
@impl true
# Confirmation sent by broker to consumer process after a Basic.cancel
def handle_info({:basic_cancel_ok, %{consumer_tag: _consumer_tag}}, state) do
{:noreply, state}
end
@impl true
def handle_info(
{:basic_deliver, payload, %{delivery_tag: tag, redelivered: redelivered} = headers},
state
) do
queue = unquote(opts).queue
Logger.info("#{__MODULE__}: receive message on queue: #{queue}")
max_retry_to_timeout =
StepFlow.Configuration.get_var_value(StepFlow.Amqp, :max_retry_to_timeout, 10)
Logger.debug("#{__MODULE__} #{inspect(headers)}")
max_retry_reached =
with headers when headers != :undefined <- Map.get(headers, :headers),
{"x-death", :array, death} <- List.keyfind(headers, "x-death", 0),
{:table, table} <- List.first(death),
{"count", :long, count} <- List.keyfind(table, "count", 0) do
count > max_retry_to_timeout
else
_ -> false
end
if max_retry_reached do
Logger.warn("#{__MODULE__}: timeout message sent to queue: #{queue}_timeout")
CommonEmitter.publish(queue <> "_timeout", payload, [], "job_response")
AMQP.Basic.ack(state.channel, tag)
else
# Check if payload is json
case payload |> Jason.decode() do
{:ok, data} ->
unquote(opts).consumer.(state.channel, tag, redelivered, data)
{:error, _} ->
Logger.error("#{__MODULE__}: Payload is not a json: #{payload}")
AMQP.Basic.reject(state.channel, tag, requeue: false)
end
end
{:noreply, state}
end
@impl true
def handle_info({:DOWN, _, :process, pid, reason}, state) do
state =
if state.auto_restart do
url = Helpers.get_amqp_connection_url()
options = Helpers.get_amqp_connection_options()
queue = unquote(opts).queue
exchange_name = unquote(opts).exchange
prefetch_count = Map.get(unquote(opts), :prefetch_count)
{:ok, state} =
CommonConsumer.rabbitmq_connect(url, options, queue, exchange_name, prefetch_count)
state
else
state
end
{:noreply, state}
end
@impl true
def terminate(_reason, state) do
CommonConsumer.rabbitmq_disconnect(state)
end
end
end
def rabbitmq_connect(url, options, queue, exchange, prefetch_count) do
case AMQP.Connection.open(url, options) do
{:ok, connection} ->
init_amqp_connection(connection, queue, exchange, prefetch_count)
{:error, message} ->
Logger.error("#{__MODULE__}: unable to connect to: #{url}, reason: #{inspect(message)}")
# Reconnection loop
:timer.sleep(10_000)
rabbitmq_connect(url, options, queue, exchange, prefetch_count)
end
end
def init_amqp_connection(connection, queue, exchange_name, prefetch_count \\ nil) do
Process.monitor(connection.pid)
{:ok, channel} = AMQP.Channel.open(connection)
if prefetch_count != nil do
:ok = AMQP.Basic.qos(channel, prefetch_count: prefetch_count)
end
CommonConsumer.create_queues(channel, queue)
Logger.warn("#{__MODULE__}: bind #{queue}")
AMQP.Queue.bind(channel, queue, exchange_name, routing_key: queue)
Logger.warn("#{__MODULE__}: connected to queue #{queue}")
{:ok, _consumer_tag} = AMQP.Basic.consume(channel, queue)
{:ok, %{channel: channel, connection: connection, auto_restart: true}}
end
def rabbitmq_disconnect(state) do
Logger.warn("#{__MODULE__}: Closing AMQP connection...")
AMQP.Connection.close(state.connection)
state
|> Map.replace(:auto_restart, false)
end
def create_queues(channel, queue) do
AMQP.Queue.declare(channel, "job_response_not_found", durable: true)
AMQP.Queue.declare(channel, queue <> "_timeout", durable: true)
AMQP.Exchange.topic(channel, "job_response",
durable: true,
arguments: [{"alternate-exchange", :longstr, "job_response_not_found"}]
)
AMQP.Exchange.topic(channel, "worker_response",
durable: true,
arguments: [{"alternate-exchange", :longstr, "worker_response_not_found"}]
)
AMQP.Queue.declare(channel, "direct_messaging_not_found", durable: true)
AMQP.Queue.declare(channel, queue <> "_timeout", durable: true)
AMQP.Exchange.declare(channel, "direct_messaging", :headers,
durable: true,
arguments: [{"alternate-exchange", :longstr, "direct_messaging_not_found"}]
)
AMQP.Exchange.fanout(channel, "job_response_delayed", durable: true)
{:ok, _job_response_delayed_queue} =
AMQP.Queue.declare(channel, "job_response_delayed",
arguments: [
{"x-message-ttl", :short, 5000},
{"x-dead-letter-exchange", :longstr, ""}
]
)
AMQP.Queue.bind(channel, "job_response_delayed", "job_response_delayed", routing_key: "*")
AMQP.Queue.declare(channel, queue,
durable: true,
arguments: [
{"x-dead-letter-exchange", :longstr, "job_response_delayed"},
{"x-dead-letter-routing-key", :longstr, queue}
]
)
end
end