puma: Phased restart crash when upgrading Rack Gem despite using 'prune_bundler'

Summary

I upgraded rack in my Gemfile and Puma/Bundler unexpectedly crashed on restart. As we use prune_bundler in our cluster and don’t preload, we expected that the Puma cluster master would be isolated from Gem changes, but able to respawn new workers which use the new Gems.

It only seems to crash if Gems directly used by Puma are upgraded. The version of Bundler doesn’t seem to matter (but not exhaustively tested.)

Have I understood the expected behaviour correctly? Any workarounds or suggestions welcomed.

How to replicate

The Production issue can be simply replicated in Development without needing to do a full deploy:

  • My Gemfile.lock specifies rack (1.5.2) and puma (2.11.2)
  • Load gems: bundle install
  • Config file as per the deployment guide
 worker 2
 threads 1
 prune_bundler              <-- The important bit
 rackup DefaultRackup
 port 3000
 environment 'development'
  • Let’s start Puma: bundle exec puma -C ./config/puma.rb
* Pruning Bundler environment                   <-- Good
[9372] Puma starting in cluster mode...
[9372] * Version 2.11.2 (ruby 2.1.2-p95), codename: Intrepid Squirrel
[9372] * Min threads: 1, max threads: 4
[9372] * Environment: development
[9372] * Process workers: 2
[9372] * Phased restart available
[9372] * Listening on tcp://0.0.0.0:3000
[9372] Use Ctrl-C to stop
[9374] + Gemfile in context: /Users/<me>/Documents/<org>/<project>/Gemfile
[9375] + Gemfile in context: /Users/<me>/Documents/<org>/<project>/Gemfile
[9372] - Worker 0 (pid: 9374) booted, phase: 0
[9372] - Worker 1 (pid: 9375) booted, phase: 0
  • Update the Gemfile.lock to use rack (1.5.3)
  • bundle install
  • Ask for a phased restart: kill -USR1 <pid of puma cluster master>
  • Crash:
/Users/<me>/.rvm/gems/ruby-2.1.2@<project>/gems/bundler-<version>/lib/bundler/runtime.rb:34: in `block in setup':
You have already activated rack 1.5.2, but your Gemfile requires rack 1.5.3.
Prepending `bundle exec` to your command may solve this. (Gem::LoadError)

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 23 (20 by maintainers)

Most upvoted comments

Hmm it’s harder than I thought. I was trying to get it to do a bundle exec inside prune_bundler before re-execing yet again without Bundler like it currently does. I don’t understand why it’s so complicated though. If you allow the Bundler environment in Puma itself, why can’t the restart simply be something like this?

Bundler.with_clean_env do
  Kernel.exec(Gem.ruby, '-S', 'bundle', 'exec', *argv)
end

I quickly hacked this in and it seems to work here but maybe I’m missing the bigger picture.