2019-04-29 20:27:42 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2022-07-27 22:27:38 -04:00
|
|
|
RSpec.describe ::Jobs::Base do
|
2019-10-02 00:01:53 -04:00
|
|
|
class GoodJob < ::Jobs::Base
|
2014-02-21 00:05:19 -05:00
|
|
|
attr_accessor :count
|
|
|
|
def execute(args)
|
|
|
|
self.count ||= 0
|
|
|
|
self.count += 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-10-02 00:01:53 -04:00
|
|
|
class BadJob < ::Jobs::Base
|
2022-08-05 03:40:22 -04:00
|
|
|
class BadJobError < StandardError
|
|
|
|
end
|
|
|
|
|
2014-02-20 23:31:15 -05:00
|
|
|
attr_accessor :fail_count
|
|
|
|
|
|
|
|
def execute(args)
|
|
|
|
@fail_count ||= 0
|
|
|
|
@fail_count += 1
|
2022-08-05 03:40:22 -04:00
|
|
|
raise BadJobError
|
2014-02-20 23:31:15 -05:00
|
|
|
end
|
|
|
|
end
|
2015-02-09 15:47:46 -05:00
|
|
|
|
2023-06-13 18:30:23 -04:00
|
|
|
class ConcurrentJob < ::Jobs::Base
|
|
|
|
cluster_concurrency 1
|
|
|
|
|
2023-07-18 21:01:30 -04:00
|
|
|
def self.stop!
|
|
|
|
@stop = true
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.stop
|
|
|
|
@stop
|
|
|
|
end
|
|
|
|
|
2023-06-13 18:30:23 -04:00
|
|
|
def self.running?
|
|
|
|
@running
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.running=(val)
|
|
|
|
@running = val
|
|
|
|
end
|
|
|
|
|
|
|
|
def execute(args)
|
|
|
|
self.class.running = true
|
2023-07-18 21:01:30 -04:00
|
|
|
sleep 0.0001 while !self.class.stop
|
2023-06-13 18:30:23 -04:00
|
|
|
ensure
|
|
|
|
self.class.running = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "handles job concurrency" do
|
|
|
|
ConcurrentJob.clear_cluster_concurrency_lock!
|
|
|
|
|
|
|
|
expect(ConcurrentJob.get_cluster_concurrency).to eq(1)
|
|
|
|
expect(BadJob.get_cluster_concurrency).to eq(nil)
|
|
|
|
|
|
|
|
expect(Sidekiq::Queues["default"].size).to eq(0)
|
|
|
|
|
|
|
|
thread = Thread.new { ConcurrentJob.new.perform({ "test" => 100 }) }
|
|
|
|
|
|
|
|
wait_for { ConcurrentJob.running? }
|
|
|
|
|
|
|
|
ConcurrentJob.new.perform({ "test" => 100 })
|
|
|
|
|
2023-12-26 01:47:03 -05:00
|
|
|
expect(Sidekiq::Queues["default"].size).to eq(1)
|
2023-06-13 18:30:23 -04:00
|
|
|
expect(Sidekiq::Queues["default"][0]["args"][0]).to eq("test" => 100)
|
2023-12-26 01:47:03 -05:00
|
|
|
ensure
|
2023-07-18 21:01:30 -04:00
|
|
|
ConcurrentJob.stop!
|
2023-06-13 18:30:23 -04:00
|
|
|
thread.join
|
|
|
|
end
|
|
|
|
|
2014-02-21 00:05:19 -05:00
|
|
|
it "handles correct jobs" do
|
|
|
|
job = GoodJob.new
|
|
|
|
job.perform({})
|
2014-12-31 09:55:03 -05:00
|
|
|
expect(job.count).to eq(1)
|
2014-02-21 00:05:19 -05:00
|
|
|
end
|
2014-02-20 23:31:15 -05:00
|
|
|
|
|
|
|
it "handles errors in multisite" do
|
2014-07-17 16:22:46 -04:00
|
|
|
RailsMultisite::ConnectionManagement.expects(:all_dbs).returns(%w[default default default])
|
|
|
|
# one exception per database
|
2015-02-09 15:47:46 -05:00
|
|
|
Discourse.expects(:handle_job_exception).times(3)
|
2014-02-20 23:31:15 -05:00
|
|
|
|
2015-02-09 15:47:46 -05:00
|
|
|
bad = BadJob.new
|
2016-05-29 23:38:04 -04:00
|
|
|
expect { bad.perform({}) }.to raise_error(Jobs::HandledExceptionWrapper)
|
2014-12-31 09:55:03 -05:00
|
|
|
expect(bad.fail_count).to eq(3)
|
2014-02-20 23:31:15 -05:00
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2022-08-05 03:40:22 -04:00
|
|
|
describe "#perform" do
|
|
|
|
context "when a job raises an error" do
|
|
|
|
before { Discourse.reset_job_exception_stats! }
|
|
|
|
|
|
|
|
after { Discourse.reset_job_exception_stats! }
|
|
|
|
|
|
|
|
it "collects stats for failing jobs in Discourse.job_exception_stats" do
|
|
|
|
bad = BadJob.new
|
|
|
|
3.times do
|
|
|
|
# During test env handle_job_exception errors out
|
|
|
|
# in production this is suppressed
|
|
|
|
expect { bad.perform({}) }.to raise_error(BadJob::BadJobError)
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(Discourse.job_exception_stats).to eq({ BadJob => 3 })
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
it "delegates the process call to execute" do
|
2022-11-02 05:47:59 -04:00
|
|
|
::Jobs::Base.any_instance.expects(:execute).with({ "hello" => "world" })
|
2023-01-10 00:41:25 -05:00
|
|
|
::Jobs::Base.new.perform("hello" => "world")
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it "converts to an indifferent access hash" do
|
2019-10-02 00:01:53 -04:00
|
|
|
::Jobs::Base.any_instance.expects(:execute).with(instance_of(HashWithIndifferentAccess))
|
2023-01-10 00:41:25 -05:00
|
|
|
::Jobs::Base.new.perform("hello" => "world")
|
2013-02-25 11:42:20 -05:00
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2020-05-28 07:52:27 -04:00
|
|
|
context "with fake jobs" do
|
|
|
|
let(:common_state) { [] }
|
|
|
|
|
|
|
|
let(:test_job_1) do
|
|
|
|
Class
|
|
|
|
.new(Jobs::Base)
|
|
|
|
.tap do |klass|
|
|
|
|
state = common_state
|
|
|
|
klass.define_method(:execute) { |args| state << "job_1_executed" }
|
|
|
|
end
|
|
|
|
end
|
2023-01-09 06:18:21 -05:00
|
|
|
|
2020-05-28 07:52:27 -04:00
|
|
|
let(:test_job_2) do
|
|
|
|
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"
|
2023-01-09 06:18:21 -05:00
|
|
|
end
|
2020-05-28 07:52:27 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "runs jobs synchronously sequentially in tests" do
|
|
|
|
Jobs.run_immediately!
|
|
|
|
Jobs.enqueue(test_job_2)
|
|
|
|
|
|
|
|
expect(common_state).to eq(%w[job_2_started job_2_finished job_1_executed])
|
|
|
|
end
|
|
|
|
end
|
2024-12-09 23:44:56 -05:00
|
|
|
|
|
|
|
context "when `Discourse.enable_sidekiq_logging?` is `true`" do
|
2024-12-10 04:01:25 -05:00
|
|
|
let(:tmp_log_file_path) { "#{Rails.root}/tmp/sidekiq_test_log.log" }
|
2024-12-09 23:44:56 -05:00
|
|
|
|
|
|
|
before do
|
|
|
|
Discourse.enable_sidekiq_logging
|
2024-12-10 04:01:25 -05:00
|
|
|
described_class::JobInstrumenter.set_log_path(tmp_log_file_path)
|
2024-12-09 23:44:56 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
after do
|
|
|
|
Discourse.disable_sidekiq_logging
|
|
|
|
described_class::JobInstrumenter.reset_log_path
|
2024-12-10 04:01:25 -05:00
|
|
|
FileUtils.rm(tmp_log_file_path)
|
2024-12-09 23:44:56 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it "should log the job in the sidekiq log file" do
|
|
|
|
job = GoodJob.new
|
|
|
|
job.perform({ some_param: "some_value" })
|
|
|
|
|
2024-12-10 04:01:25 -05:00
|
|
|
parsed_logline = JSON.parse(File.read(tmp_log_file_path).split("\n").first)
|
2024-12-09 23:44:56 -05:00
|
|
|
|
|
|
|
expect(parsed_logline["hostname"]).to be_present
|
|
|
|
expect(parsed_logline["pid"]).to be_present
|
|
|
|
expect(parsed_logline["database"]).to eq(RailsMultisite::ConnectionManagement.current_db)
|
|
|
|
expect(parsed_logline["job_name"]).to eq("GoodJob")
|
|
|
|
expect(parsed_logline["job_type"]).to eq("regular")
|
|
|
|
expect(parsed_logline["status"]).to eq("success")
|
|
|
|
expect(JSON.parse(parsed_logline["opts"])).to eq("some_param" => "some_value")
|
|
|
|
expect(parsed_logline["duration"]).to be_present
|
|
|
|
expect(parsed_logline["sql_duration"]).to eq(0)
|
|
|
|
expect(parsed_logline["sql_calls"]).to eq(0)
|
|
|
|
expect(parsed_logline["redis_duration"]).to eq(0)
|
|
|
|
expect(parsed_logline["redis_calls"]).to eq(0)
|
|
|
|
expect(parsed_logline["net_duration"]).to eq(0)
|
|
|
|
expect(parsed_logline["net_calls"]).to eq(0)
|
|
|
|
expect(parsed_logline["live_slots_finish"]).to be_present
|
|
|
|
expect(parsed_logline["live_slots"]).to be_present
|
|
|
|
end
|
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|