towerops/lib/mix/tasks/oban.cancel_stuck_discovery.ex
2026-02-03 10:19:56 -06:00

69 lines
1.8 KiB
Elixir

defmodule Mix.Tasks.Oban.CancelStuckDiscovery do
@shortdoc "Cancels stuck DiscoveryWorker jobs"
@moduledoc """
Cancels all stuck DiscoveryWorker jobs that are in scheduled or retryable state.
## Usage
# In production via kubectl:
kubectl exec -n towerops deployment/towerops -- /app/bin/towerops rpc "Mix.Tasks.Oban.CancelStuckDiscovery.run([])"
# Locally:
mix oban.cancel_stuck_discovery
"""
use Mix.Task
import Ecto.Query
require Logger
@impl Mix.Task
def run(_args) do
Mix.Task.run("app.start")
cancelled_count =
Oban.Job
|> where([j], j.worker == "Towerops.Workers.DiscoveryWorker")
|> where([j], j.state in ["scheduled", "retryable", "executing"])
|> Towerops.Repo.all()
|> Enum.map(fn job ->
case Oban.cancel_job(Oban, job.id) do
{:ok, cancelled_job} ->
Logger.info(
"Cancelled stuck discovery job",
job_id: cancelled_job.id,
device_id: get_in(cancelled_job.args, ["device_id"]),
state: cancelled_job.state,
attempted_at: cancelled_job.attempted_at
)
1
:ok ->
# Some versions of Oban return :ok instead of {:ok, job}
Logger.info(
"Cancelled stuck discovery job",
job_id: job.id,
device_id: get_in(job.args, ["device_id"]),
state: job.state,
attempted_at: job.attempted_at
)
1
{:error, reason} ->
Logger.error(
"Failed to cancel job",
job_id: job.id,
error: inspect(reason)
)
0
end
end)
|> Enum.sum()
Mix.shell().info("Cancelled #{cancelled_count} stuck discovery jobs")
end
end