Fix too many forwards (#5854)
* Avoid sending explicit Undo->Announce when original deleted * Do not forward a reply back to the server that sent it * Deduplicate inboxes of rebloggers' followers for delete forwarding * Adjust test * Fix wrong class, bad SQL, wrong variable, outdated comment
This commit is contained in:
		
							parent
							
								
									dc1ebd45a3
								
							
						
					
					
						commit
						85e97ecab6
					
				
					 6 changed files with 27 additions and 15 deletions
				
			
		| 
						 | 
					@ -210,7 +210,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def forward_for_reply
 | 
					  def forward_for_reply
 | 
				
			||||||
    return unless @json['signature'].present? && reply_to_local?
 | 
					    return unless @json['signature'].present? && reply_to_local?
 | 
				
			||||||
    ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id)
 | 
					    ActivityPub::RawDistributionWorker.perform_async(Oj.dump(@json), replied_to_status.account_id, [@account.preferred_inbox_url])
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def lock_options
 | 
					  def lock_options
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,8 +30,11 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
 | 
				
			||||||
  def forward_for_reblogs(status)
 | 
					  def forward_for_reblogs(status)
 | 
				
			||||||
    return if @json['signature'].blank?
 | 
					    return if @json['signature'].blank?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ActivityPub::RawDistributionWorker.push_bulk(status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id)) do |account_id|
 | 
					    rebloggers_ids = status.reblogs.includes(:account).references(:account).merge(Account.local).pluck(:account_id)
 | 
				
			||||||
      [payload, account_id]
 | 
					    inboxes        = Account.where(id: ::Follow.where(target_account_id: rebloggers_ids).select(:account_id)).inboxes - [@account.preferred_inbox_url]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
 | 
				
			||||||
 | 
					      [payload, rebloggers_ids.first, inbox_url]
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -214,6 +214,10 @@ class Account < ApplicationRecord
 | 
				
			||||||
    Rails.cache.fetch("exclude_domains_for:#{id}") { domain_blocks.pluck(:domain) }
 | 
					    Rails.cache.fetch("exclude_domains_for:#{id}") { domain_blocks.pluck(:domain) }
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def preferred_inbox_url
 | 
				
			||||||
 | 
					    shared_inbox_url.presence || inbox_url
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  class << self
 | 
					  class << self
 | 
				
			||||||
    def readonly_attributes
 | 
					    def readonly_attributes
 | 
				
			||||||
      super - %w(statuses_count following_count followers_count)
 | 
					      super - %w(statuses_count following_count followers_count)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
class RemoveStatusService < BaseService
 | 
					class RemoveStatusService < BaseService
 | 
				
			||||||
  include StreamEntryRenderer
 | 
					  include StreamEntryRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def call(status)
 | 
					  def call(status, options = {})
 | 
				
			||||||
    @payload      = Oj.dump(event: :delete, payload: status.id.to_s)
 | 
					    @payload      = Oj.dump(event: :delete, payload: status.id.to_s)
 | 
				
			||||||
    @status       = status
 | 
					    @status       = status
 | 
				
			||||||
    @account      = status.account
 | 
					    @account      = status.account
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ class RemoveStatusService < BaseService
 | 
				
			||||||
    @mentions     = status.mentions.includes(:account).to_a
 | 
					    @mentions     = status.mentions.includes(:account).to_a
 | 
				
			||||||
    @reblogs      = status.reblogs.to_a
 | 
					    @reblogs      = status.reblogs.to_a
 | 
				
			||||||
    @stream_entry = status.stream_entry
 | 
					    @stream_entry = status.stream_entry
 | 
				
			||||||
 | 
					    @options      = options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    remove_from_self if status.account.local?
 | 
					    remove_from_self if status.account.local?
 | 
				
			||||||
    remove_from_followers
 | 
					    remove_from_followers
 | 
				
			||||||
| 
						 | 
					@ -22,7 +23,12 @@ class RemoveStatusService < BaseService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @status.destroy!
 | 
					    @status.destroy!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return unless @account.local?
 | 
					    # There is no reason to send out Undo activities when the
 | 
				
			||||||
 | 
					    # cause is that the original object has been removed, since
 | 
				
			||||||
 | 
					    # original object being removed implicitly removes reblogs
 | 
				
			||||||
 | 
					    # of it. The Delete activity of the original is forwarded
 | 
				
			||||||
 | 
					    # separately.
 | 
				
			||||||
 | 
					    return if !@account.local? || @options[:original_removed]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    remove_from_remote_followers
 | 
					    remove_from_remote_followers
 | 
				
			||||||
    remove_from_remote_affected
 | 
					    remove_from_remote_affected
 | 
				
			||||||
| 
						 | 
					@ -104,7 +110,7 @@ class RemoveStatusService < BaseService
 | 
				
			||||||
    # without us being able to do all the fancy stuff
 | 
					    # without us being able to do all the fancy stuff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @reblogs.each do |reblog|
 | 
					    @reblogs.each do |reblog|
 | 
				
			||||||
      RemoveStatusService.new.call(reblog)
 | 
					      RemoveStatusService.new.call(reblog, original_removed: true)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,10 +5,10 @@ class ActivityPub::RawDistributionWorker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sidekiq_options queue: 'push'
 | 
					  sidekiq_options queue: 'push'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def perform(json, source_account_id)
 | 
					  def perform(json, source_account_id, exclude_inboxes = [])
 | 
				
			||||||
    @account = Account.find(source_account_id)
 | 
					    @account = Account.find(source_account_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ActivityPub::DeliveryWorker.push_bulk(inboxes) do |inbox_url|
 | 
					    ActivityPub::DeliveryWorker.push_bulk(inboxes - exclude_inboxes) do |inbox_url|
 | 
				
			||||||
      [json, @account.id, inbox_url]
 | 
					      [json, @account.id, inbox_url]
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  rescue ActiveRecord::RecordNotFound
 | 
					  rescue ActiveRecord::RecordNotFound
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,13 +30,13 @@ RSpec.describe ActivityPub::Activity::Delete do
 | 
				
			||||||
  context 'when the status has been reblogged' do
 | 
					  context 'when the status has been reblogged' do
 | 
				
			||||||
    describe '#perform' do
 | 
					    describe '#perform' do
 | 
				
			||||||
      subject { described_class.new(json, sender) }
 | 
					      subject { described_class.new(json, sender) }
 | 
				
			||||||
      let(:reblogger) { Fabricate(:account) }
 | 
					      let!(:reblogger) { Fabricate(:account) }
 | 
				
			||||||
      let(:follower)   { Fabricate(:account, username: 'follower', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 | 
					      let!(:follower)  { Fabricate(:account, username: 'follower', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 | 
				
			||||||
 | 
					      let!(:reblog)    { Fabricate(:status, account: reblogger, reblog: status) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      before do
 | 
					      before do
 | 
				
			||||||
        stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
 | 
					        stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
 | 
				
			||||||
        follower.follow!(reblogger)
 | 
					        follower.follow!(reblogger)
 | 
				
			||||||
        Fabricate(:status, account: reblogger, reblog: status)
 | 
					 | 
				
			||||||
        subject.perform
 | 
					        subject.perform
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,8 +45,7 @@ RSpec.describe ActivityPub::Activity::Delete do
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'sends delete activity to followers of rebloggers' do
 | 
					      it 'sends delete activity to followers of rebloggers' do
 | 
				
			||||||
        # one for Delete original post, and one for Undo reblog (normal delivery)
 | 
					        expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
 | 
				
			||||||
        expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.twice
 | 
					 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue