Betterment / delayed

a multi-threaded, SQL-driven ActiveJob backend used at Betterment to process millions of background jobs per day

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Job failed to load: undefined class/module Delayed::JobWrapper.

suwyn opened this issue · comments

Hello!

We're receiving the following error after upgrading to Rails 7.1.1

Job failed to load: undefined class/module Delayed::JobWrapper.
Job failed to load: undefined class/module Delayed::JobWrapper. 
Handler: \"--- !ruby/object:Delayed::JobWrapper
job_data:
  job_class: TestJob
  job_id: af7d22a1-35c9-42d8-9f57-8af524bffcc5
  provider_job_id:
  queue_name: default
  priority:
  arguments: []
  executions: 0
  exception_executions: {}
  locale: en
  timezone: UTC
  enqueued_at: '2023-10-19T16:03:27.456958896Z'
  scheduled_at:
\"
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/backend/base.rb:84:in `rescue in payload_object'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/backend/base.rb:81:in `payload_object'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/backend/base.rb:133:in `max_run_time'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/worker.rb:194:in `max_run_time'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/worker.rb:137:in `block in run'
/usr/local/lib/ruby/3.2.0/benchmark.rb:311:in `realtime'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/worker.rb:136:in `run'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/worker.rb:210:in `block in run_job'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/lifecycle.rb:61:in `block in initialize'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/lifecycle.rb:66:in `execute'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/lifecycle.rb:40:in `run_callbacks'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/worker.rb:210:in `run_job'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/worker.rb:102:in `block (4 levels) in work_off'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/plugins/connection.rb:7:in `block (3 levels) in <class:Connection>'
/usr/local/bundle/ruby/3.2.0/gems/activerecord-7.1.1/lib/active_record/connection_adapters/abstract/connection_pool.rb:229:in `with_connection'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/plugins/connection.rb:6:in `block (2 levels) in <class:Connection>'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/lifecycle.rb:79:in `block (2 levels) in add'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/lifecycle.rb:61:in `block in initialize'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/lifecycle.rb:79:in `block in add'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/lifecycle.rb:66:in `execute'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/lifecycle.rb:40:in `run_callbacks'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/worker.rb:121:in `run_thread_callbacks'
/usr/local/bundle/ruby/3.2.0/gems/delayed-0.5.1/lib/delayed/worker.rb:101:in `block (3 levels) in work_off'
/usr/local/bundle/ruby/3.2.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:352:in `run_task'
/usr/local/bundle/ruby/3.2.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:343:in `block (3 levels) in create_worker'
/usr/local/bundle/ruby/3.2.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:334:in `loop'
/usr/local/bundle/ruby/3.2.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:334:in `block (2 levels) in create_worker'
/usr/local/bundle/ruby/3.2.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:333:in `catch'
/usr/local/bundle/ruby/3.2.0/gems/concurrent-ruby-1.2.2/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:333:in `block in create_worker'

Steps to reproduce

  1. Create a new Rails Project
  2. Add Delayed
  3. Create a TestJob
     # frozen_string_literal: true
    
     class TestJob < ApplicationJob
       queue_as :default
    
       def perform
       end
     end
    
  4. Queue the job to perform later
  5. Run the worker process and observe the job failure in the delayed_jobs table

Thanks for the report @suwyn! 0.5.2 has been released with a fix.

The issue was in the load order. In local development & test (where config.eager_load = false) ActiveJob does not load until the first ActiveJob::Base class is referenced, which means that Delayed::JobWrapper class also wasn't loading by the time delayed:work attempted to deserialize the handler.

Thank you for fixing this!! I had this problem in both development and production and updating to the latest version fixed it for me :).