Skip to content

Commit 5a04667

Browse files
committed
Raise a more specific error when the job class can't be instantiated
During a rolling deploy, jobs maybe be enqueued that the job processor doesn't know about yet. Currently it's not easily possible for the job adapter to know if the exception originated from user code or not.
1 parent 8b544b2 commit 5a04667

File tree

4 files changed

+30
-1
lines changed

4 files changed

+30
-1
lines changed

activejob/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1+
* Raise a more specific error during deserialization when a previously serialized job class is now unknown.
2+
3+
`ActiveJob::UnknownJobClassError` will be raised instead of a more generic
4+
`NameError` to make it easily possible for adapters to tell if the `NameError`
5+
was raised during job execution or deserialization.
6+
7+
*Earlopain*
18

29
Please check [8-0-stable](https://github.com/rails/rails/blob/8-0-stable/activejob/CHANGELOG.md) for previous changes.

activejob/lib/active_job.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ module ActiveJob
3939
autoload :Arguments
4040
autoload :DeserializationError, "active_job/arguments"
4141
autoload :SerializationError, "active_job/arguments"
42+
autoload :UnknownJobClassError, "active_job/core"
4243
autoload :EnqueueAfterTransactionCommit
4344

4445
eager_autoload do

activejob/lib/active_job/core.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# frozen_string_literal: true
22

33
module ActiveJob
4+
# Raised during job payload deserialization when it references an uninitialized job class.
5+
class UnknownJobClassError < NameError
6+
def initialize(job_class_name)
7+
super("Failed to instantiate job, class `#{job_class_name}` doesn't exist", job_class_name)
8+
end
9+
end
10+
411
# = Active Job \Core
512
#
613
# Provides general behavior that will be included into every Active Job
@@ -60,7 +67,10 @@ def successfully_enqueued?
6067
module ClassMethods
6168
# Creates a new job instance from a hash created with +serialize+
6269
def deserialize(job_data)
63-
job = job_data["job_class"].constantize.new
70+
job_class = job_data["job_class"].safe_constantize
71+
raise UnknownJobClassError, job_data["job_class"] unless job_class
72+
73+
job = job_class.new
6474
job.deserialize(job_data)
6575
job
6676
end

activejob/test/cases/job_serialization_test.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ class JobSerializationTest < ActiveSupport::TestCase
3838
assert_equal h2.serialize, h3.serialize
3939
end
4040

41+
test "deserialize raises a specific exception on unknown `job_class`" do
42+
payload = HelloJob.new.serialize
43+
44+
# Simulate the job class being missing, for example during rolling deploys when
45+
# the server enqueues a new job but the job processor hasn't been restarted yet.
46+
payload["job_class"] = "IDontExist"
47+
assert_raises(ActiveJob::UnknownJobClassError) do
48+
HelloJob.deserialize(payload)
49+
end
50+
end
51+
4152
test "deserialize sets locale" do
4253
job = HelloJob.new
4354
job.deserialize "locale" => "es"

0 commit comments

Comments
 (0)