Skip to content

Commit e12aa98

Browse files
Skip Object.const_get entirely to prevent autoloader deadlocks
const_defined? returns true for constants registered via Module#autoload (as Zeitwerk does), so guarding const_get with const_defined? still triggers autoloading and deadlocks. Instead, skip string-to-class resolution entirely: - Registry.find_by_class: only walk ancestors when name is already a Class object; return nil for strings. Use Registry.add_alias to register subclasses that should inherit a parent's throttle. - Throttled.requeue_throttled: pass the class name string directly to Registry.get instead of resolving it with const_get. Fixes #213 Made-with: Cursor
1 parent 5f59fa8 commit e12aa98

2 files changed

Lines changed: 9 additions & 16 deletions

File tree

lib/sidekiq/throttled.rb

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,19 +96,12 @@ def throttled?(message)
9696
# Return throttled job to be executed later, delegating the details of how to do that
9797
# to the Strategy for that job.
9898
#
99-
# Uses +const_defined?+ before +const_get+ to avoid triggering
100-
# autoloading (e.g. Zeitwerk) from Sidekiq's fetcher thread, which
101-
# runs outside the Rails reloader and can deadlock or raise.
102-
#
10399
# @return [void]
104100
def requeue_throttled(work)
105101
message = JSON.parse(work.job)
106102
class_name = message.fetch("wrapped") { message.fetch("class") { return false } }
107-
return false unless Object.const_defined?(class_name)
108-
109-
job_class = Object.const_get(class_name)
110103

111-
Registry.get job_class do |strategy|
104+
Registry.get class_name do |strategy|
112105
strategy.requeue_throttled(work)
113106
end
114107
end

lib/sidekiq/throttled/registry.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,22 +99,22 @@ def find(name)
9999

100100
# Find strategy by class or it's parents.
101101
#
102-
# Uses +const_defined?+ before +const_get+ to avoid triggering
103-
# autoloading (e.g. Zeitwerk) from Sidekiq's fetcher thread, which
104-
# runs outside the Rails reloader and can deadlock or raise.
102+
# Only performs ancestor lookup when +name+ is already a Class.
103+
# String-based lookup via +Object.const_get+ is intentionally
104+
# skipped because it triggers autoloading (e.g. Zeitwerk) from
105+
# Sidekiq's fetcher thread, which runs outside the Rails reloader
106+
# and can deadlock or raise. Use +Registry.add_alias+ to register
107+
# subclasses that should inherit a parent's throttle strategy.
105108
#
106109
# @param name [Class, #to_s]
107110
# @return [Strategy, nil]
108111
def find_by_class(name)
109-
const = name.is_a?(Class) ? name : (Object.const_get(name) if Object.const_defined?(name))
110-
return unless const.is_a?(Class)
112+
return unless name.is_a?(Class)
111113

112-
const.ancestors.each do |m|
114+
name.ancestors.each do |m|
113115
strategy = find(m.name)
114116
return strategy if strategy
115117
end
116-
rescue NameError
117-
nil
118118
end
119119
end
120120
end

0 commit comments

Comments
 (0)