Current section

Files

Jump to
gnat lib gnat jetstream pager.ex
Raw

lib/gnat/jetstream/pager.ex

defmodule Gnat.Jetstream.Pager do
@moduledoc false
alias Gnat.Jetstream
alias Gnat.Jetstream.API.{Consumer, Util}
@opaque pager :: map()
@type message :: Gnat.message()
def init(conn, stream_name, opts) do
domain = Keyword.get(opts, :domainl)
consumer = %Consumer{
stream_name: stream_name,
domain: domain,
ack_policy: :all,
ack_wait: 30_000_000_000,
deliver_policy: :all,
description: "ephemeral consumer",
replay_policy: :instant,
inactive_threshold: 30_000_000_000
}
consumer = apply_opts_to_consumer(consumer, opts)
inbox = Util.reply_inbox()
with {:ok, consumer_info} <- Consumer.create(conn, consumer),
{:ok, sub} <- Gnat.sub(conn, self(), inbox) do
state = %{
conn: conn,
stream_name: stream_name,
consumer_name: consumer_info.name,
domain: domain,
inbox: inbox,
batch: Keyword.get(opts, :batch, 10),
sub: sub
}
{:ok, state}
end
end
@spec page(pager()) :: {:page, list(message())} | {:done, list(message())} | {:error, term()}
def page(state) do
with :ok <- request_next_message(state) do
receive_messages(state, [])
end
end
def cleanup(%{conn: conn} = state) do
with :ok <- Gnat.unsub(conn, state.sub) do
:ok = Consumer.delete(conn, state.stream_name, state.consumer_name, state.domain)
end
end
def reduce(conn, stream_name, opts, initial_state, fun) do
with {:ok, pager} <- init(conn, stream_name, opts) do
page_through(pager, initial_state, fun)
end
end
defp page_through(pager, state, fun) do
case page(pager) do
{:page, messages} ->
new_state = Enum.reduce(messages, state, fun)
page_through(pager, new_state, fun)
{:done, messages} ->
new_state = Enum.reduce(messages, state, fun)
:ok = cleanup(pager)
{:ok, new_state}
end
end
defp request_next_message(state) do
opts = [batch: state.batch, no_wait: true]
Consumer.request_next_message(
state.conn,
state.stream_name,
state.consumer_name,
state.inbox,
state.domain,
opts
)
end
defp receive_messages(%{batch: batch}, messages) when length(messages) == batch do
last = hd(messages)
:ok = Jetstream.ack(last)
{:page, Enum.reverse(messages)}
end
@terminals ["404", "408"]
defp receive_messages(%{sub: sid} = state, messages) do
receive do
{:msg, %{sid: ^sid, status: status}} when status in @terminals ->
{:done, Enum.reverse(messages)}
{:msg, %{sid: ^sid, reply_to: nil}} ->
{:done, Enum.reverse(messages)}
{:msg, %{sid: ^sid} = message} ->
receive_messages(state, [message | messages])
end
end
## Helpers for accepting user options
defp apply_opts_to_consumer(consumer, opts) do
case Keyword.get(opts, :from_seq) do
nil ->
consumer
seq when is_integer(seq) ->
%Consumer{consumer | deliver_policy: :by_start_sequence, opt_start_seq: seq}
end
end
end