如何解决在安装ruby gem和运行使用这些gem的脚本时的争用条件?

y53ybaqx  于 2022-11-04  发布在  Ruby
关注(0)|答案(2)|浏览(289)

我们发现了一个有趣的问题。我们的环境是通过使用ansible配置的,而ansible又会安装gem。
一些gem,我们希望版本比什么都新。例如,aws-sdk-core版本〉= 3.104。
此ansible任务运行:

gem install -v '>= 3.104' aws-sdk-core

然后,我们有一个cronjob,它每5分钟(但跨越数千台服务器)运行一个“需要aws-sdk-core”的脚本。
而且,偶尔,它会中断:

/var/lib/gems/2.5.0/gems/aws-sdk-core-3.166.0/lib/seahorse.rb:3:in `require_relative': cannot load such file -- /var/lib/gems/2.5.0/gems/aws-sdk-core-3.166.0/lib/seahorse/util (LoadError)
...

我制作了一个简单的脚本,在另一个小得多的gem上显示了这个问题:


# !/usr/bin/env ruby

# frozen_string_literal: true

require 'progressbar'
puts 1

如果您将其保存为z.rb,然后在shell中运行:while true; do ./z.rb; done,然后在另一个shell中:while true; do gem install -v '>= 1.0.0' progressbar; done,最终(一两分钟后),在运行z.rb的shell中,您将得到:

1
1
<internal:/usr/lib/ruby/vendor_ruby/rubygems/core_ext/kernel_require.rb>:85:in `require': cannot load such file -- progressbar (LoadError)
        from <internal:/usr/lib/ruby/vendor_ruby/rubygems/core_ext/kernel_require.rb>:85:in `require'
        from ./z.rb:3:in `<main>'
1
1
1

有没有什么方法可以避免这个问题,除了开始/救援和重试后1秒的睡眠(我可以这样做,但它的哦,所以丑陋)?
对我们来说,问题是我们需要至少安装一些特定的版本(如果我们提供version = SOMETHING,ansible就可以避免调用gem install,但是我们也希望安装新的版本),虽然竞争条件的窗口很小,但是有成千上万的服务器,并且cronjob每5分钟运行一次,(ansible每4小时运行一次),我们每天都会收到~打邮件,cronjob失败。

a6b3iqyw

a6b3iqyw1#

也许不是答案,但仍然...:)我认为没有比以某种方式同步任务更好的方法了。
Rubygems安装程序似乎在安装新的之前删除了现有的gem文件(如果要安装的版本存在的话,我猜)。
这很容易确认。例如,安装了两个pry版本-- 0.14.0和0.14 1 --我执行以下操作:
1.运行while true; do gem install --no-document -f -v '0.14.1' pry; done
1.在另一个shell中运行while true; do gem which pry; done
2的结果如下所示:

...
/path/to/ruby/lib/ruby/gems/2.7.0/gems/pry-0.14.1/lib/pry.rb
/path/to/ruby/lib/ruby/gems/2.7.0/gems/pry-0.14.1/lib/pry.rb
/path/to/ruby/lib/ruby/gems/2.7.0/gems/pry-0.14.0/lib/pry.rb <=== Notice this one
/path/to/ruby/lib/ruby/gems/2.7.0/gems/pry-0.14.1/lib/pry.rb
/path/to/ruby/lib/ruby/gems/2.7.0/gems/pry-0.14.1/lib/pry.rb
...
Traceback (most recent call last):
        9: from /path/to/ruby/bin/gem:9:in `<main>'
        8: from /path/to/ruby/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require'
        7: from /path/to/ruby/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require'
        6: from /path/to/ruby/lib/ruby/2.7.0/rubygems/gem_runner.rb:86:in `<top (required)>'
        5: from /path/to/ruby/lib/ruby/2.7.0/rubygems.rb:1131:in `load_plugins'
        4: from /path/to/ruby/lib/ruby/2.7.0/rubygems.rb:538:in `find_latest_files'
        3: from /path/to/ruby/lib/ruby/2.7.0/rubygems/specification.rb:1086:in `latest_specs'
        2: from /path/to/ruby/lib/ruby/2.7.0/rubygems/specification.rb:1093:in `_latest_specs'
        1: from /path/to/ruby/lib/ruby/2.7.0/rubygems/specification.rb:1093:in `reverse_each'
/path/to/ruby/lib/ruby/2.7.0/rubygems/specification.rb:1096:in `block in _latest_specs': undefined method `platform' for nil:NilClass (NoMethodError)
...
/path/to/ruby/lib/ruby/gems/2.7.0/gems/pry-0.14.1/lib/pry.rb
/path/to/ruby/lib/ruby/gems/2.7.0/gems/pry-0.14.1/lib/pry.rb
...
ERROR:  While executing gem ... (Errno::ENOENT)
    No such file or directory @ rb_sysopen - /path/to/ruby/lib/ruby/gems/2.7.0/specifications/pry-0.14.1.gemspec
...

正如人们所期望的那样--各种各样的意外(从将gem解析为以前的版本到由于我们在中间的不完整状态中捕获gem安装过程而发生的几个异常)。
除了你提到的重试之外,还可以这样做:

  • 维护一些外部锁,当gem安装开始时,这些外部锁将被提升,当gem安装完成时,这些外部锁将被释放;如果安装正在进行中,corn作业会检查它并优雅地关闭(虽然对于这种情况来说似乎不必要地复杂,但仍然如此)
  • 使用Gem API从cron作业检查gem状态,如果所需的gem不存在,则关闭
  • 抢救LoadError并正常关闭
  • 等等等等等等。
dhxwm5r4

dhxwm5r42#

在与其他人交谈时,我们想出了一个解决方案。似乎增加了这两个选项:--conservative --minimal-deps到gem安装解决了这个问题。

相关问题