|
| 1 | +defmodule PrisonRideshare.MailerRateLimiter do |
| 2 | + @behaviour Bamboo.DeliverLaterStrategy |
| 3 | + use GenServer |
| 4 | + |
| 5 | + require Logger |
| 6 | + |
| 7 | + @default_interval_ms 30000 |
| 8 | + |
| 9 | + def start_link(opts \\ []) do |
| 10 | + GenServer.start_link(__MODULE__, opts, name: __MODULE__) |
| 11 | + end |
| 12 | + |
| 13 | + @impl Bamboo.DeliverLaterStrategy |
| 14 | + def deliver_later(adapter, email, config) do |
| 15 | + GenServer.cast(__MODULE__, {:enqueue, adapter, email, config}) |
| 16 | + end |
| 17 | + |
| 18 | + @impl GenServer |
| 19 | + def init(_opts) do |
| 20 | + {:ok, |
| 21 | + %{ |
| 22 | + queue: :queue.new(), |
| 23 | + interval_ms: delivery_interval_ms(), |
| 24 | + in_flight: false |
| 25 | + }} |
| 26 | + end |
| 27 | + |
| 28 | + @impl GenServer |
| 29 | + def handle_cast({:enqueue, adapter, email, config}, state) do |
| 30 | + state = %{state | queue: :queue.in({adapter, email, config}, state.queue)} |
| 31 | + {:noreply, maybe_schedule(state)} |
| 32 | + end |
| 33 | + |
| 34 | + @impl GenServer |
| 35 | + def handle_info(:deliver_next, state) do |
| 36 | + case :queue.out(state.queue) do |
| 37 | + {{:value, {adapter, email, config}}, queue} -> |
| 38 | + deliver(adapter, email, config) |
| 39 | + state = %{state | queue: queue} |
| 40 | + |
| 41 | + if :queue.is_empty(queue) do |
| 42 | + {:noreply, %{state | in_flight: false}} |
| 43 | + else |
| 44 | + Process.send_after(self(), :deliver_next, state.interval_ms) |
| 45 | + {:noreply, state} |
| 46 | + end |
| 47 | + |
| 48 | + {:empty, _queue} -> |
| 49 | + {:noreply, %{state | in_flight: false}} |
| 50 | + end |
| 51 | + end |
| 52 | + |
| 53 | + defp maybe_schedule(%{in_flight: true} = state), do: state |
| 54 | + |
| 55 | + defp maybe_schedule(state) do |
| 56 | + send(self(), :deliver_next) |
| 57 | + %{state | in_flight: true} |
| 58 | + end |
| 59 | + |
| 60 | + defp deliver(adapter, email, config) do |
| 61 | + try do |
| 62 | + case adapter.deliver(email, config) do |
| 63 | + {:error, error} -> |
| 64 | + Logger.error("Email delivery failed: #{inspect(error)}") |
| 65 | + |
| 66 | + _ -> |
| 67 | + :ok |
| 68 | + end |
| 69 | + rescue |
| 70 | + exception -> |
| 71 | + Logger.error(Exception.format(:error, exception, __STACKTRACE__)) |
| 72 | + catch |
| 73 | + kind, reason -> |
| 74 | + Logger.error(Exception.format(kind, reason, __STACKTRACE__)) |
| 75 | + end |
| 76 | + end |
| 77 | + |
| 78 | + defp delivery_interval_ms do |
| 79 | + config = Application.get_env(:prison_rideshare, PrisonRideshare.Mailer, []) |
| 80 | + config[:rate_limit_ms] || @default_interval_ms |
| 81 | + end |
| 82 | +end |
0 commit comments