summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThe Bundler Bot <bot@bundler.io>2017-07-15 13:42:03 +0000
committerThe Bundler Bot <bot@bundler.io>2017-07-15 13:42:03 +0000
commitcdddaff6bb2951f195a78dc12910a307fbc26fe9 (patch)
tree0f3a11027fe2f5d2c7bb1064fa0e9e57d785f778
parent9e42c6096a65308c6084b42d4dfceddf7ac13444 (diff)
parent3e9dd31f0afa87b4658abab2f85ee237e7d5cac8 (diff)
downloadbundler-cdddaff6bb2951f195a78dc12910a307fbc26fe9.tar.gz
Auto merge of #5852 - stefansedich:add-process-lock, r=indirect
Adding process lock for bundle install operations ### What was the end-user problem that led to this PR? As per #5851 multiple concurrent bundle installs would cause failures when compiling ### What was your diagnosis of the problem? As described in #5851 a sample project was created demonstrating the issue when running a docker-compose solution of 2 containers using a gem cache volume ### What is your fix for the problem, implemented in this PR? The fix is to implement an installation process lock so that only one process can execute an install at one time ### Why did you choose this fix out of the possible options? I chose this fix because it was discussed in #5851 as a possible solution
-rw-r--r--lib/bundler.rb1
-rw-r--r--lib/bundler/installer.rb30
-rw-r--r--lib/bundler/process_lock.rb24
-rw-r--r--spec/install/process_lock_spec.rb22
4 files changed, 63 insertions, 14 deletions
diff --git a/lib/bundler.rb b/lib/bundler.rb
index 068970fbc6..f38fa0bf5a 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 084c49cb47..cd54320199 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.frozen?
- @definition.ensure_equivalent_gemfile_and_lockfile(options[:deployment])
- end
+ ProcessLock.lock do
+ if Bundler.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.frozen?
- Standalone.new(options[:standalone], @definition).generate if options[:standalone]
+ lock unless Bundler.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..4bd6931577
--- /dev/null
+++ b/lib/bundler/process_lock.rb
@@ -0,0 +1,24 @@
+# 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")
+ has_lock = false
+
+ File.open(lock_file_path, "w") do |f|
+ f.flock(File::LOCK_EX)
+ has_lock = true
+ yield
+ f.flock(File::LOCK_UN)
+ end
+ rescue Errno::EACCES, Errno::ENOLCK
+ # In the case the user does not have access to
+ # create the lock file or is using NFS where
+ # locks are not available we skip locking.
+ yield
+ ensure
+ FileUtils.rm_f(lock_file_path) if has_lock
+ 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