Boot the Rails environment in the Resque worker so background jobs run - #490
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
🟢 Approved
The change correctly restores Rails initialization and safely reconnects inherited resources after forking.
Pull request overview
Boots Rails for production Resque pool workers and establishes fork-safe connections.
Changes:
- Makes
resque:setupdepend on Rails’ environment. - Reconnects Redis and Active Record in forked workers.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto reengage.
File summaries
| File | Description |
|---|---|
lib/tasks/resque.rake |
Configures Rails boot and per-worker connections. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The Procfile runs the worker with `resque-pool`, whose CLI requires `resque` before the Rakefile loads `config/application`. At that point `Rails::Railtie` isn't defined yet, so resque's own railtie -- the code that would wire `resque:setup => :environment` -- is skipped, and by the time `rails/all` loads, `require "resque"` is already a no-op. The pool master therefore never boots the app: `resque:preload` eager-loads nothing (eager_load is only true once the environment is loaded), so no ActiveJob class is ever defined and every enqueued job dies with "Failed to instantiate job, class ... doesn't exist." Concretely, ActiveStorage::PurgeJob never runs, so destroying a book never purges its uploaded files -- they stay on disk and remain retrievable via the public Disk URL for the life of the instance -- and Turbo::Streams::BroadcastJob never runs either. Wire `resque:setup => :environment` in lib/tasks/resque.rake so the pool master boots the full Rails environment and eager-loads every job class before forking workers, and reconnect each forked worker to its own Redis and database connections.
d1297d3 to
c599726
Compare
|
Review threads: 0 — Copilot approved the head and raised no threads. CI green, mergeable. Converged; merge held for the operator (Writebook is source-available, kudos-only). |
The bug
The worker process is started by the
workers:line in theProcfile:resque-pool's CLIrequiresresque(viaresque/pool/tasks) before it loads theRakefile, which is what loadsconfig/application. So at the momentresqueis required,Rails::Railtieis not defined yet, and resque's own railtie — the code that would wireresque:setup => :environment— is skipped:By the time
rails/allfinally loads,require "resque"is already a no-op, so the railtie never loads. The pool master therefore invokesresque:pool → resque:preload → resque:setupwithresque:setupstill a no-op, and never boots the Rails environment.resque:preloadonly eager-loadsif Rails.application.config.eager_load, which is only true once the production environment has actually been loaded — which never happens. No ActiveJob class is ever defined in the worker, and every enqueued job dies with:The most serious consequence:
ActiveStorage::PurgeJobnever runs, so destroying a book never purges its uploaded files. They stay on disk and remain anonymously retrievable via the public Disk URL for the life of the instance, and storage grows without bound.Turbo::Streams::BroadcastJobnever runs either (collaborative-editing indicators break).Note this only affects the
resque-poolboot path used in production; a barebundle exec rake resque:workhappens to work becauserakeloadsconfig/application(and thusrails/all) before requiringresque, so the railtie loads there.The fix
Add
lib/tasks/resque.rakethat wiresresque:setup => :environmentunconditionally (independent of whether resque's railtie loaded), so the pool master boots the full Rails environment and eager-loads every job class before forking workers. Because the workers are forked from the master withFORK_PER_JOB=false, theresque:pool:setuphook hands each forked child its own Redis and database connections instead of sharing the master's.Verification
Reproduced end-to-end against
mainin the production environment (SQLite, local Disk service, Redis), using the exact Procfile commandFORK_PER_JOB=false INTERVAL=0.1 bundle exec resque-pool. In each run a throwawayActiveStorage::Blobwas uploaded andActiveStorage::PurgeJob.perform_later(blob)enqueued, then the pool ran briefly and Resque stats + the blob's file were inspected.Before the fix:
processed=1, failed=1Failed to instantiate job, classActiveStorage::PurgeJobdoesn't existAfter the fix:
processed=1, failed=0Performed ActiveStorage::PurgeJob ... in 52.27ms,Deleted file from key: ...CI gates run locally and green:
bin/rubocop,bin/brakeman --no-pager(0 warnings),bin/importmap audit,bin/rails test(277 runs, 0 failures/errors),bin/rails test:system(0 failures).