diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 80708e2aa0..37984157de 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -800,11 +800,11 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} ## # Safely write a file in binary mode on all platforms. def self.write_binary(path, data) + File.open(path, File::RDWR | File::CREAT | File::BINARY | File::LOCK_EX) do |io| + io.write data + end + rescue *WRITE_BINARY_ERRORS File.open(path, 'wb') do |io| - begin - io.flock(File::LOCK_EX) - rescue *WRITE_BINARY_ERRORS - end io.write data end rescue Errno::ENOLCK # NFS diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 38642ee8ef..8e3965ef92 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -446,13 +446,9 @@ class Gem::Installer # specifications directory. def write_spec - File.open spec_file, 'w' do |file| - spec.installed_by_version = Gem.rubygems_version + spec.installed_by_version = Gem.rubygems_version - file.puts spec.to_ruby_for_cache - - file.fsync rescue nil # for filesystems without fsync(2) - end + Gem.write_binary(spec_file, spec.to_ruby_for_cache) end ## @@ -460,9 +456,7 @@ class Gem::Installer # specifications/default directory. def write_default_spec - File.open(default_spec_file, "w") do |file| - file.puts spec.to_ruby - end + Gem.write_binary(default_spec_file, spec.to_ruby) end ## diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index 8874577aa8..dae2b070d5 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -288,6 +288,33 @@ gem 'other', version "(SyntaxError)", e.message end + def test_ensure_no_race_conditions_between_installing_and_loading_gemspecs + a, a_gem = util_gem 'a', 2 + + Gem::Installer.at(a_gem).install + + t1 = Thread.new do + 5.times do + Gem::Installer.at(a_gem).install + sleep 0.1 + end + end + + t2 = Thread.new do + _, err = capture_output do + 20.times do + Gem::Specification.load(a.spec_file) + Gem::Specification.send(:clear_load_cache) + end + end + + assert_empty err + end + + t1.join + t2.join + end + def test_ensure_loadable_spec_security_policy pend 'openssl is missing' unless Gem::HAVE_OPENSSL