8

I've been scratching my head with this one for close to 2 weeks. I have an Ubuntu 14.04 server with rbenv installed running a number of different Rails websites, some of them on older versions of Rails, some of them on the latest version.

I have 2 websites in particular that both require a different version of puma_worker_killer, 1 requires 0.1.0 and the other 0.1.1. Both these websites use Ruby 2.5.3.

When I start the server with RAILS_ENV=dev3 bundle exec pumactl -F ./config/puma.rb start I get the following error in the logs and the website hangs:

You have already activated puma_worker_killer 0.1.1, but your Gemfile requires puma_worker_killer 0.1.0. Prepending `bundle exec` to your command may solve this. (Gem::LoadError)

At first I thought it may've been an issue with rbenv as I had the gems installed in ~/.gem instead of in ~/.rbenv so I've nuked all rubies in ~/.gem and installed them freshly into the correct rbenv folder with bundle install and I still get this same issue.

Now at this point I want to clarify that I've done extensive research on this subject online and I know that I can do many things to solve this.

I know I can just change the version and bundle update puma_worker_killer.

I also know I can remove the latest version by doing gem uninstall puma_worker_killer and choosing 0.1.1 but this would mean the dependencies on the other website wouldn't be met.

I've done some digging into the source code of bundler and can see that it's caused by the following line of code:

return if activated_spec.version == spec.version

When running in the context of bundler using bundle exec both the activated_spec and spec match, meaning that the following code in that method (check_for_activated_spec!) doesn't run. For some reason, when running the command above to start the server, activated_spec (the activated gem) is the latest version (0.1.1) and not the one listed in the Gemfile (0.1.0), meaning it doesn't return and throws the error above.

I should also mention that there also seems to be the same issue with get_process_mem, which is one of the dependencies of puma_worker_killer. It complains of already activating 0.2.5 but my Gemfile wants 0.2.4:

You have already activated get_process_mem 0.2.5, but your Gemfile requires get_process_mem 0.2.4. Prepending `bundle exec` to your command may solve this. (Gem::LoadError)

It is my understanding of bundler that it should load the version listed in the Gemfile when using bundle exec to counteract this very problem of having multiple versions of the same gem.

I know I could also create a separate gemset (which can be done with rbenv apparently) that has different versions of puma_worker_killer in them and then run rbenv local 2.5.3-pwk0.1.0 or rbenv local 2.5.3-pwk0.1.1 depending on the version I want, inside the project, but that seems overkill for what I want to achieve.

At this rate I'm tempted to just update all websites with the latest version of both puma_worker_killer and get_process_mem and then lock them in and remove all older versions on the server, but I don't think I should have to do that.

Does anyone know what's happening here or whether I'm doing something glaringly wrong?

Below is the piece of code I use to use puma_worker_killer in my puma config.

before_fork do
  require 'puma_worker_killer'

  PumaWorkerKiller.config do |config|
    config.ram           = 1024 # mb
    config.frequency     = 5 # seconds
    config.percent_usage = 0.98
    config.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds
  end

  PumaWorkerKiller.start
end
Arran Scott
  • 175
  • 5
  • 16
  • 1
    I usually set my environment inside docker for each project, so the setup won't interfere with each other. It's also not that hard to mount a directory from host machine to the docker instance, though maintaining the image may be a little bit hard sometimes. In that case I usually set up a fresh docker and mount from the host directory again to start over. – darkash Nov 28 '19 at 08:34
  • It's worth deleting your Gemfile.lock file and then running bundle install again - it will remove all the version numbers that aren't specified and try to build a set of gems that are all compatible with each other, throwing an error and explaining the clashes if it's not possible – Mark Nov 28 '19 at 08:47
  • @darkash yeah docker would be the ideal way to go with this. That's in the pipeline for future improvements but I'm just trying to overcome this immediate issue so I can get all websites to behave nicely with each other on the same server – Arran Scott Nov 28 '19 at 08:47
  • 1
    @Mark yeah, I know I could do that too but that's only similar to running `bundle update puma_worker_killer` isn't it really since it will just use the newest version of puma_worker_killer that's installed on the server – Arran Scott Nov 28 '19 at 08:53
  • related: https://github.com/rubygems/rubygems/issues/1309 https://github.com/bundler/bundler/issues/758 – Zia Ul Rehman Mughal Dec 03 '19 at 08:50
  • Why not have a different installation path for each project using the `path` option during bundle install? [https://bundler.io/v1.17/bundle_install.html](https://bundler.io/v1.17/bundle_install.html) – Ramkumar K R Dec 04 '19 at 19:59
  • @RamkumarKR I did have that in each project before but I removed it as I read that rbenv doesn't work when a custom `path` option is set during a bundle install. It seems a bit overkill to have many different paths with almost the same gems installed too. As I say above, this server has a lot of different websites hosted on it, so installing the gems in a separate folder per project would solve it, but it would also increase the load on the server dramatically, which is not what I want to do. – Arran Scott Dec 05 '19 at 09:42
  • Did you try `bundler binstub`? https://bundler.io/man/bundle-binstubs.1.html – dezull Dec 08 '19 at 03:26
  • When you say same server, are you running multiple instances of puma or a single instance of puma for both websites? Is puma on different ports? If it is multiple instances, you should have no problem running multiple versions of gems. If it on the same process, I don't see it possible to have multiple versions of the gem loaded at the same time. – PressingOnAlways Dec 08 '19 at 04:42
  • @PressingOnAlways these are multiple instances of Puma for all websites. For x number of websites we start up a new process and tag it in the puma.rb config file that lives in each project, then we monitor these using monit. Yes, all these websites run on different ports. They all use the same version of `pumactl` to start the websites though, that can't be the problem can it? – Arran Scott Dec 08 '19 at 10:32
  • @dezull yep, tried that, no luck unfortunately. – Arran Scott Dec 08 '19 at 10:34
  • I'd really look into that `pumactl` script. Can you start puma directly with `RAILS_ENV=dev3 bundle exec puma`? If yes, then it is likely the `pumactl` script spawns a new shell where the environment created by `bundle exec` is ignored. – tosch Mar 10 '20 at 20:08
  • @tosch yeah I can start it manually but I don't have the issue anymore as I manually upgraded all websites to use the latest version of `puma_worker_killer` and `get_process_mem`. It wasn't the best option but it is the only way I could get it to work. I think you could be on to something with `pumactl` though. I think when the `puma` process is started, somewhere down the line it isn't taking into account what the current activated gem is for the current bundle, I'm not 100% sure though. – Arran Scott Mar 11 '20 at 08:34

1 Answers1

1

What is happening here is basically you have several versions of the gem in your system.

Most of the time it did not cause issues because bundle exec will dynamically load required versions for your application.

In some cases gems will have binary files included. It such case bundle exec will not help because you can have only one version linked in one moment.

Basically, if you want to call the binary by alias you have to use separate gemset for each application.

If you want to keep all the gems in one place you can call binary files directly.

In your case it will be:

RAILS_ENV=dev3 bundle exec pumactl _0.1.0_ -F ./config/puma.rb

The _<version>_ construction allows you to specify version of the binary file you want to run.

You can create your custom binary as well, like fake_pumactl inside the project which will check the Gemfile.lock and automatically proxy your call to the library and specify version automatically for you. Another way is to parse gem version by shell script and put this script instead of _<version>_ in your call.

Here is the brief example

$ gem install puma
Fetching puma-4.3.3.gem

$ gem install puma -v 4.3.0
Fetching puma-4.3.0.gem

$ pumactl -v
4.3.3

$ pumactl _4.3.0_ -v
4.3.0

$ ruby -v
ruby 2.6.3p62

$ export puma_version=_4.3.0_
$ pumactl ${puma_version} -v
4.3.0

puma_version variable can be defined from result of a bash command which will extract the gem version from Gemfile.lock.

achempion
  • 794
  • 6
  • 17
  • Thanks for your answer. I know I could go down the route of creating a separate Gemset but I didn't really want to do that. I like the way you mentioned of specifying a specific binary file with the version. I've just tried it however and it doesn't seem to work. On this one specific app I have Puma 3.7.0 as the version specified in Gemfile, but also have 4.1.0 installed. I run the following command: ```RAILS_ENV=dev3 bundle exec pumactl _4.1.0_ -F ./config/puma.rb start``` and it starts 3.7.0 instead and not 4.1.0 (```Version 3.7.0 (ruby 2.5.3-p105), codename: Snowy Sagebrush```) – Arran Scott Apr 20 '20 at 08:46
  • Can you also explain to me how you'd do this, please? ```You can create your custom binary as well, like fake_pumactl inside the project which will check the Gemfile.lock and automatically proxy your call to the library and specify version automatically for you.``` – Arran Scott Apr 20 '20 at 08:53
  • I've extended the answer – achempion Apr 20 '20 at 10:46
  • Thanks. Weirdly, this works when I'm outside the root of a Rails project but then when I cd into one of the dirs it doesn't work... ```➜ app_root git:(develop) ✗ gem install puma -v 3.12.0 Building native extensions. This could take a while... Successfully installed puma-3.12.0 Done installing documentation for puma after 1 seconds 1 gem installed ➜ app_root git:(develop) ✗ pumactl _3.12.0_ -v 3.7.0``` I need to figure out why that is but you've actually answered my question, so thanks and I'll mark it as answered – Arran Scott Apr 20 '20 at 12:59
  • You could replace `pumactl ${puma_version} -v` with `${pumactl_path} -v` as well, should work inside project directory where `pumactl_path` will be defined as path to a binary inside gem forlder. Maybe `Gemfile.lock` influence outcome of your command because it contains older version. – achempion Apr 21 '20 at 09:20