diff --git a/app/jobs/base.rb b/app/jobs/base.rb index b4e1f2e77b0..da1623f8161 100644 --- a/app/jobs/base.rb +++ b/app/jobs/base.rb @@ -281,8 +281,12 @@ module Jobs end end - def self.enqueue(job_name, opts = {}) - klass = "::Jobs::#{job_name.to_s.camelcase}".constantize + def self.enqueue(job, opts = {}) + if job.instance_of?(Class) + klass = job + else + klass = "::Jobs::#{job.to_s.camelcase}".constantize + end # Unless we want to work on all sites unless opts.delete(:all_sites) @@ -319,7 +323,30 @@ module Jobs klass.new.perform(opts) end else - klass.new.perform(opts) + # Run the job synchronously + # But never run a job inside another job + # That could cause deadlocks during test runs + queue = Thread.current[:discourse_nested_job_queue] + outermost_job = !queue + + if outermost_job + queue = Queue.new + Thread.current[:discourse_nested_job_queue] = queue + end + + queue.push([klass, opts]) + + if outermost_job + # responsible for executing the queue + begin + until queue.empty? + queued_klass, queued_opts = queue.pop(true) + queued_klass.new.perform(queued_opts) + end + ensure + Thread.current[:discourse_nested_job_queue] = nil + end + end end end diff --git a/spec/jobs/jobs_base_spec.rb b/spec/jobs/jobs_base_spec.rb index 473710ba4bf..0779666865f 100644 --- a/spec/jobs/jobs_base_spec.rb +++ b/spec/jobs/jobs_base_spec.rb @@ -47,4 +47,41 @@ describe ::Jobs::Base do ::Jobs::Base.new.perform('hello' => 'world', 'sync_exec' => true) end + context "with fake jobs" do + let(:common_state) { [] } + + let(:test_job_1) { + Class.new(Jobs::Base).tap do |klass| + state = common_state + klass.define_method(:execute) do |args| + state << "job_1_executed" + end + end + } + + let(:test_job_2) { + Class.new(Jobs::Base).tap do |klass| + state = common_state + job_1 = test_job_1 + klass.define_method(:execute) do |args| + state << "job_2_started" + Jobs.enqueue(job_1) + state << "job_2_finished" + end + end + } + + it "runs jobs synchronously sequentially in tests" do + Jobs.run_immediately! + Jobs.enqueue(test_job_2) + + expect(common_state).to eq([ + "job_2_started", + "job_2_finished", + "job_1_executed" + ]) + end + + end + end