Search code examples
rubymongoiddelayed-job

Delayed::DeserializationError undefined method `has_key?'


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


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'
      # ...
    }