mirror of
https://github.com/discourse/discourse.git
synced 2024-11-24 09:17:30 +08:00
8a3d9d7036
When running jobs in tests, we use `Jobs.run_immediately!`. This means that jobs are run synchronously when they are enqueued. Jobs sometimes enqueue other jobs, which are also executed synchronously. This means that the outermost job will block until the inner jobs have finished executing. In some cases (e.g. process_post with hotlinked images) this can lead to a deadlock. This commit changes the behavior slightly. Now we will never run jobs inside other jobs. Instead, we will queue them up and run them sequentially in the order they were enqueued. As a whole, they are still executed synchronously. Consider the example ```ruby class Jobs::InnerJob < Jobs::Base def execute(args) puts "Running inner job" end end class Jobs::OuterJob < Jobs::Base def execute(args) puts "Starting outer job" Jobs.enqueue(:inner_job) puts "Finished outer job" end end Jobs.enqueue(:outer_job) puts "All jobs complete" ``` The old behavior would result in: ``` Starting outer job Running inner job Finished outer job All jobs complete ``` The new behavior will result in: ``` Starting outer job Finished outer job Running inner job All jobs complete ```
88 lines
2.1 KiB
Ruby
88 lines
2.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
describe ::Jobs::Base do
|
|
class GoodJob < ::Jobs::Base
|
|
attr_accessor :count
|
|
def execute(args)
|
|
self.count ||= 0
|
|
self.count += 1
|
|
end
|
|
end
|
|
|
|
class BadJob < ::Jobs::Base
|
|
attr_accessor :fail_count
|
|
|
|
def execute(args)
|
|
@fail_count ||= 0
|
|
@fail_count += 1
|
|
raise StandardError
|
|
end
|
|
end
|
|
|
|
it 'handles correct jobs' do
|
|
job = GoodJob.new
|
|
job.perform({})
|
|
expect(job.count).to eq(1)
|
|
end
|
|
|
|
it 'handles errors in multisite' do
|
|
RailsMultisite::ConnectionManagement.expects(:all_dbs).returns(['default', 'default', 'default'])
|
|
# one exception per database
|
|
Discourse.expects(:handle_job_exception).times(3)
|
|
|
|
bad = BadJob.new
|
|
expect { bad.perform({}) }.to raise_error(Jobs::HandledExceptionWrapper)
|
|
expect(bad.fail_count).to eq(3)
|
|
end
|
|
|
|
it 'delegates the process call to execute' do
|
|
::Jobs::Base.any_instance.expects(:execute).with('hello' => 'world')
|
|
::Jobs::Base.new.perform('hello' => 'world', 'sync_exec' => true)
|
|
end
|
|
|
|
it 'converts to an indifferent access hash' do
|
|
::Jobs::Base.any_instance.expects(:execute).with(instance_of(HashWithIndifferentAccess))
|
|
::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
|