0
0
Fork 0

Refactor reply-fetching code and disable it by default (#34147)

This commit is contained in:
Claire 2025-03-12 11:28:06 +01:00 committed by GitHub
parent 46e13dd81c
commit 9db26db495
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 15 additions and 35 deletions

View file

@ -90,8 +90,8 @@ SESSION_RETENTION_PERIOD=31556952
# Fetch All Replies Behavior # Fetch All Replies Behavior
# -------------------------- # --------------------------
# When a user expands a post (DetailedStatus view), fetch all of its replies # When a user expands a post (DetailedStatus view), fetch all of its replies
# (default: true if unset, set explicitly to ``false`` to disable) # (default: false)
FETCH_REPLIES_ENABLED=true FETCH_REPLIES_ENABLED=false
# Period to wait between fetching replies (in minutes) # Period to wait between fetching replies (in minutes)
FETCH_REPLIES_COOLDOWN_MINUTES=15 FETCH_REPLIES_COOLDOWN_MINUTES=15

View file

@ -159,8 +159,8 @@ module JsonLdHelper
# @param uri [String] # @param uri [String]
# @param id_is_known [Boolean] # @param id_is_known [Boolean]
# @param on_behalf_of [nil, Account] # @param on_behalf_of [nil, Account]
# @param raise_on_error [Boolean, Symbol<:all, :temporary>] See {#fetch_resource_without_id_validation} for possible values # @param raise_on_error [Symbol<:all, :temporary, :none>] See {#fetch_resource_without_id_validation} for possible values
def fetch_resource(uri, id_is_known, on_behalf_of = nil, raise_on_error: false, request_options: {}) def fetch_resource(uri, id_is_known, on_behalf_of = nil, raise_on_error: :none, request_options: {})
unless id_is_known unless id_is_known
json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error) json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error)
@ -185,17 +185,17 @@ module JsonLdHelper
# #
# @param uri [String] # @param uri [String]
# @param on_behalf_of [nil, Account] # @param on_behalf_of [nil, Account]
# @param raise_on_error [Boolean, Symbol<:all, :temporary>] # @param raise_on_error [Symbol<:all, :temporary, :none>]
# - +true+, +:all+ - raise if response code is not in the 2** range # - +:all+ - raise if response code is not in the 2xx range
# - +:temporary+ - raise if the response code is not an "unsalvageable error" like a 404 # - +:temporary+ - raise if the response code is not an "unsalvageable error" like a 404
# (see {#response_error_unsalvageable} ) # (see {#response_error_unsalvageable} )
# - +false+ - do not raise, return +nil+ # - +:none+ - do not raise, return +nil+
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_error: false, request_options: {}) def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_error: :none, request_options: {})
on_behalf_of ||= Account.representative on_behalf_of ||= Account.representative
build_request(uri, on_behalf_of, options: request_options).perform do |response| build_request(uri, on_behalf_of, options: request_options).perform do |response|
raise Mastodon::UnexpectedResponseError, response if !response_successful?(response) && ( raise Mastodon::UnexpectedResponseError, response if !response_successful?(response) && (
[true, :all].include?(raise_on_error) || raise_on_error == :all ||
(!response_error_unsalvageable?(response) && raise_on_error == :temporary) (!response_error_unsalvageable?(response) && raise_on_error == :temporary)
) )

View file

@ -4,7 +4,7 @@ module Status::FetchRepliesConcern
extend ActiveSupport::Concern extend ActiveSupport::Concern
# enable/disable fetching all replies # enable/disable fetching all replies
FETCH_REPLIES_ENABLED = ENV.key?('FETCH_REPLIES_ENABLED') ? ENV['FETCH_REPLIES_ENABLED'] == 'true' : true FETCH_REPLIES_ENABLED = ENV['FETCH_REPLIES_ENABLED'] == 'true'
# debounce fetching all replies to minimize DoS # debounce fetching all replies to minimize DoS
FETCH_REPLIES_COOLDOWN_MINUTES = (ENV['FETCH_REPLIES_COOLDOWN_MINUTES'] || 15).to_i.minutes FETCH_REPLIES_COOLDOWN_MINUTES = (ENV['FETCH_REPLIES_COOLDOWN_MINUTES'] || 15).to_i.minutes
@ -40,14 +40,4 @@ module Status::FetchRepliesConcern
fetched_replies_at.nil? || fetched_replies_at <= FETCH_REPLIES_COOLDOWN_MINUTES.ago fetched_replies_at.nil? || fetched_replies_at <= FETCH_REPLIES_COOLDOWN_MINUTES.ago
) )
end end
def unsubscribed?
return false if local?
!Follow.joins(:account).exists?(
target_account: account.id,
account: { domain: nil },
created_at: ..updated_at
)
end
end end

View file

@ -83,13 +83,13 @@ class ActivityPub::FetchRemoteStatusService < BaseService
def fetch_status(uri, id_is_known, on_behalf_of = nil) def fetch_status(uri, id_is_known, on_behalf_of = nil)
begin begin
fetch_resource(uri, id_is_known, on_behalf_of, raise_on_error: true) fetch_resource(uri, id_is_known, on_behalf_of, raise_on_error: :all)
rescue Mastodon::UnexpectedResponseError => e rescue Mastodon::UnexpectedResponseError => e
return unless e.response.code == 404 return unless e.response.code == 404
# If this is a 404 from a status from an account that has no local followers, delete it # If this is a 404 from a public status from a remote account, delete it
existing_status = Status.find_by(uri: uri) existing_status = Status.remote.find_by(uri: uri)
if !existing_status.nil? && existing_status.unsubscribed? && existing_status.distributable? if existing_status&.distributable?
Rails.logger.debug { "FetchRemoteStatusService - Got 404 for orphaned status with URI #{uri}, deleting" } Rails.logger.debug { "FetchRemoteStatusService - Got 404 for orphaned status with URI #{uri}, deleting" }
Tombstone.find_or_create_by(uri: uri, account: existing_status.account) Tombstone.find_or_create_by(uri: uri, account: existing_status.account)
RemoveStatusService.new.call(existing_status, redraft: false) RemoveStatusService.new.call(existing_status, redraft: false)

View file

@ -85,7 +85,6 @@ RSpec.describe Status::FetchRepliesConcern do
it 'shows the status as unsubscribed' do it 'shows the status as unsubscribed' do
expect(Status.unsubscribed).to eq([status]) expect(Status.unsubscribed).to eq([status])
expect(status.unsubscribed?).to be(true)
end end
end end
@ -96,7 +95,6 @@ RSpec.describe Status::FetchRepliesConcern do
it 'shows the status as unsubscribed' do it 'shows the status as unsubscribed' do
expect(Status.unsubscribed).to eq([status]) expect(Status.unsubscribed).to eq([status])
expect(status.unsubscribed?).to be(true)
end end
end end
@ -107,7 +105,6 @@ RSpec.describe Status::FetchRepliesConcern do
it 'shows the status as unsubscribed' do it 'shows the status as unsubscribed' do
expect(Status.unsubscribed).to eq([status]) expect(Status.unsubscribed).to eq([status])
expect(status.unsubscribed?).to be(true)
end end
end end
@ -118,14 +115,12 @@ RSpec.describe Status::FetchRepliesConcern do
it 'does not show the status as unsubscribed' do it 'does not show the status as unsubscribed' do
expect(Status.unsubscribed).to eq([]) expect(Status.unsubscribed).to eq([])
expect(status.unsubscribed?).to be(false)
end end
end end
context 'when the status has no followers' do context 'when the status has no followers' do
it 'shows the status as unsubscribed' do it 'shows the status as unsubscribed' do
expect(Status.unsubscribed).to eq([status]) expect(Status.unsubscribed).to eq([status])
expect(status.unsubscribed?).to be(true)
end end
end end
end end

View file

@ -296,12 +296,6 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
it_behaves_like 'no delete' it_behaves_like 'no delete'
end end
end end
context 'when the status is from an account with local followers' do
let(:follow) { Fabricate(:follow, account: follower, target_account: sender, created_at: 2.days.ago) }
it_behaves_like 'no delete'
end
end end
end end

View file

@ -123,6 +123,7 @@ RSpec.describe ActivityPub::FetchAllRepliesWorker do
end end
before do before do
stub_const('Status::FetchRepliesConcern::FETCH_REPLIES_ENABLED', true)
allow(FetchReplyWorker).to receive(:push_bulk) allow(FetchReplyWorker).to receive(:push_bulk)
all_items.each do |item| all_items.each do |item|
next if [top_note_uri, reply_note_uri].include? item next if [top_note_uri, reply_note_uri].include? item