pleroma/lib/pleroma/workers/publisher_worker.ex
Lain Soykaf 04fbb59932
fix(federation): cancel queued deliveries to blocked instances
Delivery jobs only checked reachability, so a job queued (or retrying)
before the admin rejected or quarantined an instance was still sent.
Check should_federate? again when the job runs.
2026-10-27 12:51:14 +04:00

89 lines
2.4 KiB
Elixir

# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PublisherWorker do
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Instances
alias Pleroma.Web.ActivityPub.Publisher
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
use Oban.Worker, queue: :federator_outgoing, max_attempts: 13
@publish_one_param_keys %{
"activity_id" => :activity_id,
"cc" => :cc,
"inbox" => :inbox,
"unreachable_since" => :unreachable_since
}
@impl true
def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do
activity = Activity.get_by_id(activity_id)
federator().perform(:publish, activity)
end
def perform(%Job{args: %{"op" => "publish_one", "params" => params}} = job) do
params =
Enum.reduce(params, %{}, fn {key, value}, acc ->
case @publish_one_param_keys do
%{^key => atom_key} -> Map.put(acc, atom_key, value)
_ -> acc
end
end)
cond do
# Cancel / skip the job if this server believed to be unreachable now
not Instances.reachable?(params.inbox) ->
{:cancel, :unreachable}
# The instance may have been blocked after the job was queued
not federate?(params) ->
{:cancel, :rejected}
true ->
case federator().perform(:publish_one, params) do
{:ok, _} ->
:ok
{:error, _} = error ->
# Only mark as unreachable on final failure
if job.attempt == job.max_attempts do
Instances.set_unreachable(params.inbox)
end
error
error ->
# Unexpected error, may have been client side
error
end
end
end
defp federate?(%{inbox: inbox, activity_id: activity_id}) do
case Activity.get_by_id(activity_id) do
%Activity{} = activity -> Publisher.should_federate?(inbox, Visibility.public?(activity))
nil -> true
end
end
@impl true
def timeout(_job), do: :timer.seconds(10)
defp federator, do: Config.get([:pipeline, :federator], Federator)
@base_backoff 15
@pow 5
@impl true
def backoff(%Job{attempt: attempt}) when is_integer(attempt) do
backoff =
:math.pow(attempt, @pow) +
@base_backoff +
:rand.uniform(2 * @base_backoff) * attempt
trunc(backoff)
end
end