BURs: process BURs sequentially in a single job.

Change the way BURs are processed. Before, we spawned a background job
for each line of the BUR, then processed each job sequentially. Now, we
process the entire BUR sequentially in a single background job.

This means that:

* BURs are truly sequential now. Before certain things like removing
  aliases weren't actually performed in a background job, so they were
  performed out-of-order before everything else in the BUR.

* Before, if an alias or implication line failed, then subsequent alias
  or implication lines would still be processed. This was because each
  alias or implication line was queued as a separate job, so a failure
  of one job didn't block another. Now, if any alias or implication
  fails, the entire BUR will fail and stop processing after that line.
  This may be good or bad, depending on whether we actually need the BUR
  to be processed in order or not.

* Before, BURs were processed inside a database transaction (except for the
  actual updating of posts). Now they're not. This is because we can't
  afford to hold transactions open while processing long-running aliases
  or implications. This means that if BUR fails in the middle when it is
  initially approved, it will be left in a half-complete state. Before
  it would be rolled back and left in a pending state with no changes
  performed.

* Before, only one BUR at a time could be processed. If multiple BURs
  were approved at the same time, then they would queue up and be
  processed one at a time. Now, multiple BURs can be processed at the
  same time. This may be undesirable when processing large BURs, or BURs
  that must be approved in a specific order.

* Before, large tag category changes could time out. This was because
  they weren't actually performed in a background job. Now they are, so
  they shouldn't time out.
This commit is contained in:
evazion
2021-09-19 18:36:12 -05:00
parent 96c5c346ad
commit 9ba84efc07
9 changed files with 87 additions and 99 deletions

View File

@@ -16,11 +16,11 @@ class BulkUpdateRequestProcessor
attr_reader :bulk_update_request
delegate :script, :forum_topic, to: :bulk_update_request
delegate :script, :forum_topic, :approver, to: :bulk_update_request
validate :validate_script
validate :validate_script_length
# @param bulk_update_request [String] the BUR
# @param bulk_update_request [BulkUpdateRequest] the BUR
def initialize(bulk_update_request)
@bulk_update_request = bulk_update_request
end
@@ -130,50 +130,45 @@ class BulkUpdateRequestProcessor
end
end
# Apply the script.
# @param approver [User] the approver of the request
def process!(approver)
ActiveRecord::Base.transaction do
commands.map do |command, *args|
case command
when :create_alias
TagAlias.approve!(antecedent_name: args[0], consequent_name: args[1], approver: approver, forum_topic: forum_topic)
# Schedule the bulk update request to be processed later, in the background.
def process_later!
ProcessBulkUpdateRequestJob.perform_later(bulk_update_request)
end
when :create_implication
TagImplication.approve!(antecedent_name: args[0], consequent_name: args[1], approver: approver, forum_topic: forum_topic)
# Process the bulk update request immediately.
def process!
commands.map do |command, *args|
case command
when :create_alias
TagAlias.approve!(antecedent_name: args[0], consequent_name: args[1], approver: approver, forum_topic: forum_topic)
when :remove_alias
tag_alias = TagAlias.active.find_by!(antecedent_name: args[0], consequent_name: args[1])
tag_alias.reject!(User.system)
when :create_implication
TagImplication.approve!(antecedent_name: args[0], consequent_name: args[1], approver: approver, forum_topic: forum_topic)
when :remove_implication
tag_implication = TagImplication.active.find_by!(antecedent_name: args[0], consequent_name: args[1])
tag_implication.reject!(User.system)
when :remove_alias
tag_alias = TagAlias.active.find_by!(antecedent_name: args[0], consequent_name: args[1])
tag_alias.reject!(User.system)
when :mass_update
TagBatchChangeJob.perform_later(args[0], args[1])
when :remove_implication
tag_implication = TagImplication.active.find_by!(antecedent_name: args[0], consequent_name: args[1])
tag_implication.reject!(User.system)
when :nuke
# Reject existing implications from any other tag to the one we're nuking
# otherwise the tag won't be removed from posts that have those other tags
if PostQueryBuilder.new(args[0]).is_simple_tag?
TagImplication.active.where(consequent_name: args[0]).each { |ti| ti.reject!(User.system) }
TagImplication.active.where(antecedent_name: args[0]).each { |ti| ti.reject!(User.system) }
end
when :mass_update
BulkUpdateRequestProcessor.mass_update(args[0], args[1])
TagBatchChangeJob.perform_later(args[0], "-#{args[0]}")
when :nuke
BulkUpdateRequestProcessor.nuke(args[0])
when :rename
TagRenameJob.perform_later(args[0], args[1])
when :rename
TagMover.new(args[0], args[1], user: User.system).move!
when :change_category
tag = Tag.find_or_create_by_name(args[0])
tag.update!(category: Tag.categories.value_for(args[1]))
when :change_category
tag = Tag.find_or_create_by_name(args[0])
tag.update!(category: Tag.categories.value_for(args[1]))
else
# should never happen
raise Error, "Unknown command: #{command}"
end
else
# should never happen
raise Error, "Unknown command: #{command}"
end
end
end
@@ -239,6 +234,31 @@ class BulkUpdateRequestProcessor
end.join("\n")
end
def self.nuke(tag_name)
# Reject existing implications from any other tag to the one we're nuking
# otherwise the tag won't be removed from posts that have those other tags
if PostQueryBuilder.new(tag_name).is_simple_tag?
TagImplication.active.where(consequent_name: tag_name).each { |ti| ti.reject!(User.system) }
TagImplication.active.where(antecedent_name: tag_name).each { |ti| ti.reject!(User.system) }
end
mass_update(tag_name, "-#{tag_name}")
end
def self.mass_update(antecedent, consequent, user: User.system)
normalized_antecedent = PostQueryBuilder.new(antecedent).split_query
normalized_consequent = PostQueryBuilder.new(consequent).parse_tag_edit
CurrentUser.scoped(user) do
Post.anon_tag_match(normalized_antecedent.join(" ")).find_each do |post|
post.with_lock do
tags = (post.tag_array - normalized_antecedent + normalized_consequent).join(" ")
post.update(tag_string: tags)
end
end
end
end
# Tag move is allowed if:
#
# * The antecedent tag is an artist tag.