diff --git a/lib/que/job_methods.rb b/lib/que/job_methods.rb index 0e07b2c4..edc525fc 100644 --- a/lib/que/job_methods.rb +++ b/lib/que/job_methods.rb @@ -12,8 +12,10 @@ module Que %{ UPDATE public.que_jobs SET error_count = error_count + 1, - expired_at = now() - WHERE id = $1::bigint + expired_at = now(), + last_error_message = left($1::text, 500), + last_error_backtrace = left($2::text, 10000) + WHERE id = $3::bigint } SQL[:destroy_job] = @@ -101,7 +103,15 @@ def expire return unless que_target if id = que_target.que_attrs[:id] - Que.execute :expire_job, [id] + values = [] + + if e = que_target.que_error + values << "#{e.class}: #{e.message}".slice(0, 500) << e.backtrace.join("\n").slice(0, 10000) + else + values << nil << nil + end + + Que.execute :expire_job, values << id end que_target.que_resolved = true diff --git a/lib/que/worker.rb b/lib/que/worker.rb index 00ef19aa..b138c982 100644 --- a/lib/que/worker.rb +++ b/lib/que/worker.rb @@ -152,8 +152,15 @@ def work_job(metajob) max_retry_count = job_class.resolve_que_setting(:maximum_retry_count) + last_error_message = "#{error.class}: #{error.message}".slice(0, 500) + last_error_backtrace = (error.backtrace || []).join("\n").slice(0, 10000) + if max_retry_count && error_count > max_retry_count - Que.execute :expire_job, [job.fetch(:id)] + Que.execute :expire_job, [ + last_error_message, + last_error_backtrace, + job.fetch(:id), + ] else delay = job_class. @@ -164,8 +171,8 @@ def work_job(metajob) Que.execute :set_error, [ delay, - "#{error.class}: #{error.message}".slice(0, 500), - (error.backtrace || []).join("\n").slice(0, 10000), + last_error_message, + last_error_backtrace, job.fetch(:id), ] end diff --git a/spec/que/job_spec.rb b/spec/que/job_spec.rb index 53492fe3..46c4a626 100644 --- a/spec/que/job_spec.rb +++ b/spec/que/job_spec.rb @@ -194,6 +194,33 @@ def run end end + it "should store error when a job expires after maximum retry count is exceeded" do + TestJobClass.class_eval do + def run + raise "Uh-oh!" + end + end + + TestJobClass.maximum_retry_count = 0 + + assert_raises(StandardError) do + enqueue_method.call + execute + end + + if should_persist_job + assert_empty active_jobs_dataset + refute_empty expired_jobs_dataset + + job = expired_jobs_dataset.first + assert_equal 1, job[:error_count] + assert_equal "RuntimeError: Uh-oh!", job[:last_error_message] + assert_match(/\A#{__FILE__}/, job[:last_error_backtrace].split("\n").first) + else + assert_empty jobs_dataset + end + end + it "should make it easy to override the default resolution action" do TestJobClass.class_eval do def run diff --git a/spec/que/worker_spec.rb b/spec/que/worker_spec.rb index c5851701..d4ec6fad 100644 --- a/spec/que/worker_spec.rb +++ b/spec/que/worker_spec.rb @@ -310,7 +310,7 @@ def run(*args) end end - it "should mark the job as expired" do + it "should mark the job as expired and store error" do job = WorkerJob.enqueue assert_equal 1, jobs_dataset.update(error_count: 14) @@ -324,13 +324,15 @@ def run(*args) run_jobs - a = jobs_dataset.select_map([:expired_at, :error_count]) + a = jobs_dataset.select_map([:expired_at, :error_count, :last_error_message, :last_error_backtrace]) assert_equal 1, a.length - expired_at, error_count = a.first + expired_at, error_count, last_error_message, last_error_backtrace = a.first assert_in_delta expired_at, Time.now, QueSpec::TIME_SKEW assert_equal 16, error_count + assert_equal last_error_message, "RuntimeError: Blah!" + assert_match(/\A#{__FILE__}/, last_error_backtrace) end describe "when that value is custom" do @@ -376,9 +378,12 @@ def run(*args) assert_equal 1, ds.update(error_count: 15) run_jobs - expired_at, error_count = ds.select_map([:expired_at, :error_count]).first + expired_at, error_count, last_error_message, last_error_backtrace = + ds.select_map([:expired_at, :error_count, :last_error_message, :last_error_backtrace]).first assert_in_delta expired_at, Time.now, QueSpec::TIME_SKEW assert_equal 16, error_count + assert_equal "NameError: uninitialized constant NonexistentJobClass", last_error_message + refute_empty last_error_backtrace end end