towerops/lib/mix/tasks/oban.cancel_stuck_discovery.ex
Graham McIntire 0350ced8e1 dialyzer: fix remaining 88 warnings — clean dialyzer run
Categories addressed:
- pattern_match / pattern_match_cov (32): remove dead case/with clauses
  that dialyzer proved unreachable from the caller types.
- contract_supertype / extra_range / invalid_contract /
  contract_with_opaque (25): narrow @spec declarations to match actual
  success typings.
- call / call_without_opaque (18): fix bad calls, narrow User.t to
  allow nil for in-memory changeset structs, suppress Ecto.Multi
  opaque-type false positives with targeted @dialyzer directives.
- guard_fail / no_return / unused_fun / unknown_function (13): remove
  dead || fallbacks, simplify always-true params, cascade-resolve
  no_returns via the underlying pattern_match and call fixes.

Real production bug fixed: StormDetector.handle_cast/2 had swapped
`:queue.in` args (`queue |> :queue.in(ts)` which desugars to
`:queue.in(queue, ts)` — wrong argument order). Alert timestamps
were never being enqueued, so storm detection would fail at runtime.
Corrected to `ts |> :queue.in(queue)`.

.dialyzer_ignore.exs: suppress two genuine dep-PLT gaps
(:ranch.get_addr/1 false positive from Bandit's transitive ranch,
and the Cloak.Vault GenServer callback_info on the CI build path).

`mix dialyzer` now: Total errors: 114, Skipped: 114 — passes clean.
Warnings: 88 → 0.
2026-04-21 10:32:42 -05:00

47 lines
1.2 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 ->
:ok = Oban.cancel_job(Oban, job.id)
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
end)
|> Enum.sum()
Mix.shell().info("Cancelled #{cancelled_count} stuck discovery jobs")
end
end