diff options
author | Stefan Sedich <stefan.sedich@gmail.com> | 2017-07-08 11:37:43 -0700 |
---|---|---|
committer | Stefan Sedich <stefan.sedich@gmail.com> | 2017-07-11 09:48:28 -0700 |
commit | 0c4c413aeec1c24899b78e277fb2094ae5509d07 (patch) | |
tree | 12c30b54d82592e67f31394553b6034ac403e4b1 | |
parent | 9324c84671292c24f598a367708edd03722b6b5b (diff) | |
download | bundler-0c4c413aeec1c24899b78e277fb2094ae5509d07.tar.gz |
Adding process lock for bundle install operations to fix issue where multiple concurrent bundle install operations fail to complete
-rw-r--r-- | lib/bundler.rb | 1 | ||||
-rw-r--r-- | lib/bundler/installer.rb | 30 | ||||
-rw-r--r-- | lib/bundler/process_lock.rb | 21 | ||||
-rw-r--r-- | spec/install/process_lock_spec.rb | 22 |
4 files changed, 60 insertions, 14 deletions
diff --git a/lib/bundler.rb b/lib/bundler.rb index b0f6869423..9baa5fd788 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -40,6 +40,7 @@ module Bundler autoload :LazySpecification, "bundler/lazy_specification" autoload :LockfileParser, "bundler/lockfile_parser" autoload :MatchPlatform, "bundler/match_platform" + autoload :ProcessLock, "bundler/process_lock" autoload :RemoteSpecification, "bundler/remote_specification" autoload :Resolver, "bundler/resolver" autoload :Retry, "bundler/retry" diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 78a73eeedd..cbd5b6ec20 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -68,23 +68,25 @@ module Bundler def run(options) create_bundle_path - if Bundler.settings[:frozen] - @definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment]) - end + ProcessLock.lock do + if Bundler.settings[:frozen] + @definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment]) + end - if @definition.dependencies.empty? - Bundler.ui.warn "The Gemfile specifies no dependencies" - lock - return - end + if @definition.dependencies.empty? + Bundler.ui.warn "The Gemfile specifies no dependencies" + lock + return + end - resolve_if_needed(options) - ensure_specs_are_compatible! - warn_on_incompatible_bundler_deps - install(options) + resolve_if_needed(options) + ensure_specs_are_compatible! + warn_on_incompatible_bundler_deps + install(options) - lock unless Bundler.settings[:frozen] - Standalone.new(options[:standalone], @definition).generate if options[:standalone] + lock unless Bundler.settings[:frozen] + Standalone.new(options[:standalone], @definition).generate if options[:standalone] + end end def generate_bundler_executable_stubs(spec, options = {}) diff --git a/lib/bundler/process_lock.rb b/lib/bundler/process_lock.rb new file mode 100644 index 0000000000..3f848fe516 --- /dev/null +++ b/lib/bundler/process_lock.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Bundler + class ProcessLock + def self.lock(bundle_path = Bundler.bundle_path) + lock_file_path = File.join(bundle_path, "bundler.lock") + + File.open(lock_file_path, "w") do |f| + f.flock(File::LOCK_EX) + + yield + end + rescue Errno::ENOLCK # NFS + raise if Thread.main != Thread.current + + yield + ensure + File.delete(lock_file_path) if File.exist?(lock_file_path) + end + end +end diff --git a/spec/install/process_lock_spec.rb b/spec/install/process_lock_spec.rb new file mode 100644 index 0000000000..113fd37934 --- /dev/null +++ b/spec/install/process_lock_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +RSpec.describe "process lock spec" do + describe "when an install operation is already holding a process lock" do + it "will not run a second concurrent bundle install until the lock is released" do + thread = Thread.new do + Bundler::ProcessLock.lock(default_bundle_path) do + sleep 1 # ignore quality_spec + expect(the_bundle).not_to include_gems "rack 1.0" + end + end + + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + thread.join + expect(the_bundle).to include_gems "rack 1.0" + end + end +end |