【问题标题】:How to solve race condition when installing ruby gems and running scripts that use these gems?安装 ruby​​ gem 和运行使用这些 gem 的脚本时如何解决竞争条件?
【发布时间】:2022-11-02 21:57:35
【问题描述】:

我们发现了有趣的问题。我们的环境是使用 ansible 配置的,它反过来安装 gems。

一些宝石,我们想要比某些东西更新的版本。例如,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 的 shell 中得到。 RB:

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,但我们也希望安装新版本),而比赛的窗口条件很小,有数千台服务器,并且每 5 分钟运行一次的 cronjob(ansible 每 4 小时运行一次),我们每天会收到大约十几封 cronjob 失败的邮件。

【问题讨论】:

    标签: ruby rubygems race-condition


    【解决方案1】:

    也许不是答案,但仍然...... :) 我认为没有比以某种方式同步任务更好的方法了。

    Rubygems 安装程序似乎在安装新的之前remove existing gem files(如果要安装的版本存在,我猜)。

    这很容易确认。例如,安装了 2 个pry 版本——0.14.0 和 0.14 1——我执行以下操作:

    1. 运行while true; do gem install --no-document -f -v '0.14.1' pry; done
    2. 在另一个 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 API 从 cron 作业检查 gem 状态,如果所需的 gem 不存在则关闭
      • 拯救LoadError并优雅关闭
      • 等等等等等等。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-08
      • 2011-01-28
      • 1970-01-01
      • 1970-01-01
      • 2020-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多