Question

I have a bug only in production. I can't reproduce the problem in development env. I have a main class that initializes several class for process data in a background job, but this is not going well in delayed_job, here are my two classes simplified.

Handler class

 class Handler
   def initialize(setting)
      @setting = setting
   end

   def process
     0.upto(11) do |index|
       # data = some code in local method...
       launch_process(data)
     end
   end 

   protected

   def launch_process(data)
    Process.new(
      @setting,
      data: data
    ).boot
   end
 end

Process class with called by Handler class

class Process
  def initialize(setting, options = {})
    @setting = setting
    options.each { |k,v| instance_variable_set("@#{k}", v) }
  end

  def boot
    self.delay.launch_retrieve
  end
end

But when the process class is executed, I have a error by delayed_job : Delayed::DeserializationError: Job failed to load: undefined method has_key? for nil:NilClass. I don't understand why initialize of class returned this error.

Any idea ? Thanks

Was it helpful?

Solution

Sometimes when we upgrade libs delayed jobs still keep old references.

Try to find the id of delayed_job in logs and play to parse its handler to ruby to find the wrong reference

j = DelayedJob.find(XXX)
data = YAML.load_dj(j.handler)
data.to_ruby

I made a pull request to help with this problem.

Meanwhile you can use this lines

# config/initializers/delayed_job.rb

# Monkey patch to use old class references
module Psych

  class << self; attr_accessor :old_class_references end
  @old_class_references = {}

  class ClassLoader
    private

    def find klassname
      klassname = ::Psych.old_class_references[klassname] || klassname
      @cache[klassname] ||= resolve(klassname)
    end
  end

  module Visitors
    class ToRuby < Psych::Visitors::Visitor
      def revive klass, node
        if klass.is_a? String
          klassname = ::Psych.old_class_references[klass] || klass
          klass = Kernel.const_get(klassname) rescue klassname
        end
        s = register(node, klass.allocate)
        init_with(s, revive_hash({}, node), node)
      end
    end
  end
end

# Add all old dependencies (hash keys) pointing to new references (hash values)
Psych.old_class_references = {
  'ActiveRecord::AttributeSet' => 'ActiveModel::AttributeSet'
  # ...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top