From 667fde17d711650c80217a358fd75b5d92b13d59 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 27 Jan 2026 14:49:06 -0600 Subject: [PATCH] discovery deduplication --- lib/towerops/workers/discovery_worker.ex | 48 ++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/lib/towerops/workers/discovery_worker.ex b/lib/towerops/workers/discovery_worker.ex index 48f891f5..09a381d9 100644 --- a/lib/towerops/workers/discovery_worker.ex +++ b/lib/towerops/workers/discovery_worker.ex @@ -16,7 +16,9 @@ defmodule Towerops.Workers.DiscoveryWorker do If no agent is assigned or the agent is offline, discovery falls back to direct SNMP operations from the Phoenix cluster. """ - use Oban.Worker, queue: :discovery + use Oban.Worker, + queue: :discovery, + unique: [period: 60, states: [:available, :scheduled, :executing]] alias Towerops.Agents alias Towerops.Devices @@ -94,11 +96,49 @@ defmodule Towerops.Workers.DiscoveryWorker do @doc """ Enqueues a discovery job for a device. + + Uses Oban unique constraint to prevent duplicate jobs within 60 seconds. """ def enqueue(device_id) do - %{device_id: device_id} - |> new() - |> Oban.insert() + # Log who's enqueueing this to track down runaway triggers + caller_module = + self() + |> Process.info(:current_stacktrace) + |> elem(1) + |> Enum.at(1) + |> elem(0) + + result = + %{device_id: device_id} + |> new() + |> Oban.insert() + + case result do + {:ok, %Oban.Job{conflict?: true}} -> + Logger.debug( + "Discovery job already enqueued for device, skipping duplicate", + device_id: device_id, + caller: caller_module + ) + + {:ok, _job} -> + Logger.debug( + "Discovery job enqueued for device", + device_id: device_id, + caller: caller_module + ) + + {:error, _} = error -> + Logger.warning( + "Failed to enqueue discovery job", + device_id: device_id, + caller: caller_module + ) + + error + end + + result end # Private helpers