diff --git a/plugins/automation/app/models/discourse_automation/automation.rb b/plugins/automation/app/models/discourse_automation/automation.rb index 18bf10581d2..b0e9ec33121 100644 --- a/plugins/automation/app/models/discourse_automation/automation.rb +++ b/plugins/automation/app/models/discourse_automation/automation.rb @@ -134,11 +134,23 @@ module DiscourseAutomation def trigger!(context = {}) if enabled - if scriptable.background && !running_in_background - trigger_in_background!(context) - else - triggerable&.on_call&.call(self, serialized_fields) - scriptable.script.call(context, serialized_fields, self) + if active_id = DiscourseAutomation.get_active_automation + Rails.logger.warn(<<~TEXT.strip) + [automation] potential automations infinite loop detected: skipping automation #{self.id} because automation #{active_id} is still executing.") + TEXT + return + end + + begin + DiscourseAutomation.set_active_automation(self.id) + if scriptable.background && !running_in_background + trigger_in_background!(context) + else + triggerable&.on_call&.call(self, serialized_fields) + scriptable.script.call(context, serialized_fields, self) + end + ensure + DiscourseAutomation.set_active_automation(nil) end end end diff --git a/plugins/automation/plugin.rb b/plugins/automation/plugin.rb index f45eabf8dff..0a690210e87 100644 --- a/plugins/automation/plugin.rb +++ b/plugins/automation/plugin.rb @@ -28,6 +28,14 @@ module ::DiscourseAutomation AUTO_RESPONDER_TRIGGERED_IDS = "auto_responder_triggered_ids" USER_GROUP_MEMBERSHIP_THROUGH_BADGE_BULK_MODIFY_START_COUNT = 1000 + + def self.set_active_automation(id) + @active_automation_id = id + end + + def self.get_active_automation + @active_automation_id + end end require_relative "lib/discourse_automation/engine" diff --git a/plugins/automation/spec/integration/infinite_loop_protection_spec.rb b/plugins/automation/spec/integration/infinite_loop_protection_spec.rb new file mode 100644 index 00000000000..170f5e406ab --- /dev/null +++ b/plugins/automation/spec/integration/infinite_loop_protection_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative "../discourse_automation_helper" + +describe "Infinite loop protection" do + fab!(:automation_1) do + Fabricate(:automation, script: "auto_responder", trigger: "post_created_edited", enabled: true) + end + + fab!(:automation_2) do + Fabricate(:automation, script: "auto_responder", trigger: "post_created_edited", enabled: true) + end + + before do + SiteSetting.discourse_automation_enabled = true + + automation_1.upsert_field!( + "word_answer_list", + "key-value", + { value: [{ key: "", value: "this is the reply" }].to_json }, + ) + automation_2.upsert_field!( + "word_answer_list", + "key-value", + { value: [{ key: "", value: "this is the reply" }].to_json }, + ) + + automation_1.upsert_field!( + "answering_user", + "user", + { value: Fabricate(:user).username }, + target: "script", + ) + automation_2.upsert_field!( + "answering_user", + "user", + { value: Fabricate(:user).username }, + target: "script", + ) + end + + it "prevents infinite loop of 2 auto_responder automations triggering each other" do + expect do + PostCreator.create!(Fabricate(:user), raw: "post", title: "topic", skip_validations: true) + end.to change { Post.count }.by(3) + end +end diff --git a/plugins/automation/spec/models/automation_spec.rb b/plugins/automation/spec/models/automation_spec.rb index 838ed261375..f2ffcca0bf7 100644 --- a/plugins/automation/spec/models/automation_spec.rb +++ b/plugins/automation/spec/models/automation_spec.rb @@ -46,16 +46,6 @@ describe DiscourseAutomation::Automation do Jobs::DiscourseAutomationTrigger.jobs.size }.by(1) end - - it "also runs the script properly" do - Jobs.run_immediately! - post = Fabricate(:post) - user = post.user - list = capture_contexts { automation.trigger!({ post: post, user: user, test: :test }) } - expect(list[0]["post"].id).to eq(post.id) - expect(list[0]["user"].id).to eq(user.id) - expect(list[0]["test"]).to eq(:test) - end end describe "#detach_custom_field" do