Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Process lock can let two `bundle install` runs in at once when a third process is waiting

Aperta
#9,944 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
35/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
ruby
Ambito
backend, devtools

Direzione di ricerca

Start by reading Gem.open_file_with_lock and Bundler::ProcessLock, whose relationship and lock-file cleanup are described in the issue; reproduce the race with the supplied Ruby script. The issue points to #9930 as a proposed fix that preserves cleanup, so read that change for context. Done means waiting processes stay mutually exclusive while lock-file cleanup is preserved, with the reproduction no longer reporting that C acquired the lock while B held it.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Bundler
Describe the problem as clearly as you can

Gem.open_file_with_lock does not keep processes out once three or more of them contend for the same lock. Bundler::ProcessLock, which wraps bundle install, is built on it. So concurrent bundle install runs that share a BUNDLE_PATH can end up installing at the same time and corrupt each other. The resulting errors change from run to run, for example:

  • Gem::Package::FormatError: unexpected end of file in .../cache/<gem>.gem
  • Zlib::GzipFile::Error: unexpected end of file
  • LoadError: cannot load such file -- .../ext/json/ext/simd/conf.rb, from an extconf.rb running against a half-extracted gem

The lock file is removed in an ensure after the lock has been released. flock(2) locks the open file, not its path, which allows this sequence:

  1. Process A holds the lock on the lock file.
  2. Process B opens the same lock file and blocks in flock.
  3. A releases the lock and removes the lock file. B acquires the lock on a file that no longer has a name.
  4. Process C opens the lock path, creates a new file there, and locks it right away because nobody else has it.
  5. B and C now run at the same time.

Step 4 needs C to arrive after A has removed the file. That is why processes that all start at the same moment never trigger it, and why it shows up as intermittent CI failures.

The removal of the lock file was added in #7939 so that .lock files are not left next to binstubs. A fix that keeps that cleanup is proposed in #9930. (I opened the fix with my personal account, this issue has been filed under my work account - sorry for the confusion).

Did you try upgrading rubygems & bundler?

Yes. The bug reproduces with the latest release, RubyGems 4.0.22, and with RubyGems 3.7.2. The reproduction below calls RubyGems directly, so the Bundler version does not change the result. Bundler's own copy of the method, used on RubyGems older than 3.5.18, has the same problem.

Post steps to reproduce the problem

The script below needs only Ruby. It installs no gems and uses no network. It starts three processes in the order described above and checks whether C gets the lock while B still holds it. The timing uses sleeps that leave wide margins, and it gave the same result on every run.

  1. Save this as repro.rb:

    require "rubygems"
    require "tmpdir"
    
    puts "RubyGems #{Gem::VERSION}"
    
    Dir.mktmpdir do |dir|
      resource = File.join(dir, "resource")
      b_inside = File.join(dir, "b_inside")
    
      # A takes the lock and holds it for 1 second
      a = fork { Gem.open_file_with_lock(resource) { sleep 1 } }
      sleep 0.3
    
      # B arrives while A holds the lock, so it waits on A's lock file
      b = fork do
        Gem.open_file_with_lock(resource) do
          File.write(b_inside, "")
          sleep 2
          File.delete(b_inside)
        end
      end
    
      # C arrives after A is done, while B holds the lock
      sleep 1.5
      c = fork do
        Gem.open_file_with_lock(resource) do
          if File.exist?(b_inside)
            puts "BUG: C acquired the lock while B was still holding it"
          else
            puts "OK: C acquired the lock only after B released it"
          end
        end
      end
    
      [a, b, c].each {|pid| Process.wait(pid) }
    end
    
  2. Run it with any affected RubyGems.

The script uses fork, so it runs on Linux and macOS but not Windows. I ran it on macOS only.

Which command did you run?
ruby repro.rb

In real use, the trigger is several bundle install commands sharing a BUNDLE_PATH where one starts after another has finished and a third is still waiting, as in parallel CI jobs on one machine.

What were you expecting to happen?

C waits until B releases the lock:

RubyGems 4.0.22
Waiting for another process to let go of lock: /tmp/.../resource.lock
Waiting for another process to let go of lock: /tmp/.../resource.lock
OK: C acquired the lock only after B released it
What happened instead?
RubyGems 4.0.22
Waiting for another process to let go of lock: /var/folders/.../T/d20261012-66303-dmd4qq/resource.lock
BUG: C acquired the lock while B was still holding it

C never waits. It creates a new lock file and gets in while B is still inside.

If not included with the output of your command, run bundle env and paste the output below
Bundler       4.0.20
  Platforms   ruby, arm64-darwin-25
Ruby          4.0.7p0 (2026-09-15 revision 229531a6cfbf07e3caef30dbac24a2a3f3fed482) [arm64-darwin-25]
  Full Path   /nix/store/m806kgwj2gz1rrf4mfhvn4swbky3rnn3-ruby-4.0.7/bin/ruby
  Config Dir  /nix/store/m806kgwj2gz1rrf4mfhvn4swbky3rnn3-ruby-4.0.7/etc
RubyGems      3.7.2
  Gem Home    /Users/<user>/.gem/ruby/4.0.0
  Gem Path    /Users/<user>/.gem/ruby/4.0.0:/nix/store/m806kgwj2gz1rrf4mfhvn4swbky3rnn3-ruby-4.0.7/lib/ruby/gems/4.0.0
  User Home   /Users/<user>
  User Path   /Users/<user>/.gem/ruby/4.0.0
  Bin Dir     /Users/<user>/.gem/ruby/4.0.0/bin
Tools
  Git         2.54.0 (Apple Git-157)
  RVM         not installed
  rbenv       not installed
  chruby      not installed

The same result was reproduced on this Ruby with RubyGems 4.0.22 loaded in place of the bundled 3.7.2.

Lingua principale
Ruby
Stelle
4k
Fork
1.9k
Merge medio
1g 9h
PR unite (30g)
37

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di ruby/rubygems

Tutte le issue di ruby/rubygems

Issue simili

Altre issue su Ruby

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.