Previous commit (3c988a5f) switched oban_pro to the licensed hex repo at deploy time. Reverting that — keep all three Pro packages vendored so prod image builds don't depend on oban.pro reachability or auth on every CI run. oban_pro 1.7.0 dropped into vendor/oban_pro via `mix hex.package fetch oban_pro 1.7.0 --repo=oban --unpack`. mix.exs goes back to `path: "vendor/oban_pro"` (matching oban_met / oban_web, which were already vendored — and stay vendored since the licensed repo only has older versions of those: 0.1.11 / 2.10.6 vs the 1.1.0 / 2.12.1 we vendor). Schema migration from3c988a5fstays — 1.7.0 tables/indexes are already applied. No code changes.
123 lines
2.9 KiB
Elixir
123 lines
2.9 KiB
Elixir
defmodule Oban.Pro.Refresher do
|
|
@moduledoc false
|
|
|
|
# Centralized refreshing and cleaning of producer records across all Oban instances.
|
|
#
|
|
# The Refresher runs as a single process supervised by the Oban.Pro application. It refreshes
|
|
# producer records for all actively running queues on the current node.
|
|
|
|
use GenServer
|
|
|
|
import Ecto.Query, only: [where: 3]
|
|
|
|
alias __MODULE__, as: State
|
|
alias Oban.{Peer, Repo}
|
|
alias Oban.Pro.Producer
|
|
|
|
require Logger
|
|
|
|
defstruct [
|
|
:timer,
|
|
interval: :timer.seconds(15),
|
|
producers: %{},
|
|
producer_ttl: :timer.minutes(1)
|
|
]
|
|
|
|
@doc false
|
|
def start_link(opts \\ []) do
|
|
state = struct!(State, opts)
|
|
|
|
GenServer.start_link(__MODULE__, state, name: __MODULE__)
|
|
end
|
|
|
|
# GenServer Callbacks
|
|
|
|
@impl GenServer
|
|
def init(state) do
|
|
Process.flag(:trap_exit, true)
|
|
|
|
{:ok, schedule_refresh(state)}
|
|
end
|
|
|
|
@impl GenServer
|
|
def terminate(_reason, state) do
|
|
if is_reference(state.timer), do: Process.cancel_timer(state.timer)
|
|
|
|
:ok
|
|
end
|
|
|
|
@impl GenServer
|
|
def handle_call(:refresh, _from, state) do
|
|
{:noreply, state} = handle_info(:refresh, state)
|
|
|
|
{:reply, :ok, state}
|
|
end
|
|
|
|
@impl GenServer
|
|
def handle_info(:refresh, state) do
|
|
try do
|
|
refresh_producers(state)
|
|
cleanup_producers(state)
|
|
rescue
|
|
error ->
|
|
Logger.warning(
|
|
message: "Producer refresh failed",
|
|
source: :oban_pro,
|
|
module: __MODULE__,
|
|
reason: error
|
|
)
|
|
end
|
|
|
|
{:noreply, schedule_refresh(state)}
|
|
end
|
|
|
|
# Helpers
|
|
|
|
defp schedule_refresh(state) do
|
|
timer = Process.send_after(self(), :refresh, state.interval)
|
|
|
|
%{state | timer: timer}
|
|
end
|
|
|
|
defp refresh_producers(_state) do
|
|
match = [{{{:"$1", {:producer, :"$2"}}, :_, :_}, [], [[:"$1", :"$2"]]}]
|
|
|
|
Oban.Registry
|
|
|> Registry.select(match)
|
|
|> Enum.group_by(&hd/1, &Enum.at(&1, 1))
|
|
|> Enum.each(fn {oban_name, queues} ->
|
|
with {_pid, %{testing: :disabled} = conf} <- Oban.Registry.lookup(oban_name) do
|
|
now = DateTime.utc_now()
|
|
ids = Enum.flat_map(queues, &lookup_uuid(&1, oban_name))
|
|
|
|
refresh_query = where(Producer, [p], p.uuid in ^ids)
|
|
|
|
Repo.update_all(conf, refresh_query, set: [updated_at: now])
|
|
end
|
|
end)
|
|
end
|
|
|
|
defp cleanup_producers(state) do
|
|
match = [{{:"$1", :_, :"$2"}, [{:not, {:is_tuple, :"$1"}}], [:"$2"]}]
|
|
|
|
Oban.Registry
|
|
|> Registry.select(match)
|
|
|> Enum.filter(&Peer.leader?/1)
|
|
|> Enum.each(fn conf ->
|
|
now = DateTime.utc_now()
|
|
outdated_at = DateTime.add(now, -state.producer_ttl, :millisecond)
|
|
cleanup_query = where(Producer, [p], p.updated_at <= ^outdated_at)
|
|
|
|
Repo.delete_all(conf, cleanup_query)
|
|
end)
|
|
end
|
|
|
|
defp lookup_uuid(queue, oban_name) do
|
|
prod_name = {oban_name, {:producer, queue}}
|
|
|
|
case Registry.meta(Oban.Registry, prod_name) do
|
|
{:ok, %{uuid: uuid}} -> [uuid]
|
|
_error -> []
|
|
end
|
|
end
|
|
end
|