aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Nagel2013-01-23 00:26:25 -0600
committerJack Nagel2013-01-26 12:14:45 -0600
commitf82ed2f33dede59e6044a5b42e7c5bec6f7bbe26 (patch)
treea7bdd4052d161c001335c7ecdd1d3acc233a7bed
parentd1dca30bef8ea067b8868379e28e270b59518ac8 (diff)
downloadhomebrew-f82ed2f33dede59e6044a5b42e7c5bec6f7bbe26.tar.bz2
FormulaInstaller: implement installation locks
FormulaInstaller now attempts to take a lock on a "foo.brewing" file for the formula and all of its dependencies before attempting installation. The lock is an advisory lock implemented using flock(), and as such it only locks out other processes that attempt to take the lock. It also means that it is never necessary to manually remove the lock file, because the lock is not enforced by I/O. The uninstall, link, and unlink commands all learn to respect this lock as well, so that the installation cannot be corrupted by a concurrent Homebrew process, and keg operations cannot occur simultaneously.
-rw-r--r--Library/Homebrew/cmd/link.rb6
-rw-r--r--Library/Homebrew/cmd/uninstall.rb10
-rw-r--r--Library/Homebrew/cmd/unlink.rb6
-rw-r--r--Library/Homebrew/exceptions.rb12
-rw-r--r--Library/Homebrew/formula.rb15
-rw-r--r--Library/Homebrew/formula_installer.rb26
-rw-r--r--Library/Homebrew/keg.rb12
7 files changed, 79 insertions, 8 deletions
diff --git a/Library/Homebrew/cmd/link.rb b/Library/Homebrew/cmd/link.rb
index a85690ecb..6371c41de 100644
--- a/Library/Homebrew/cmd/link.rb
+++ b/Library/Homebrew/cmd/link.rb
@@ -35,8 +35,10 @@ module Homebrew extend self
next
end
- print "Linking #{keg}... " do
- puts "#{keg.link(mode)} symlinks created"
+ keg.lock do
+ print "Linking #{keg}... " do
+ puts "#{keg.link(mode)} symlinks created"
+ end
end
end
end
diff --git a/Library/Homebrew/cmd/uninstall.rb b/Library/Homebrew/cmd/uninstall.rb
index d719be96a..19ad6eeb5 100644
--- a/Library/Homebrew/cmd/uninstall.rb
+++ b/Library/Homebrew/cmd/uninstall.rb
@@ -7,10 +7,12 @@ module Homebrew extend self
if not ARGV.force?
ARGV.kegs.each do |keg|
- puts "Uninstalling #{keg}..."
- keg.unlink
- keg.uninstall
- rm_opt_link keg.fname
+ keg.lock do
+ puts "Uninstalling #{keg}..."
+ keg.unlink
+ keg.uninstall
+ rm_opt_link keg.fname
+ end
end
else
ARGV.named.each do |name|
diff --git a/Library/Homebrew/cmd/unlink.rb b/Library/Homebrew/cmd/unlink.rb
index 12b037781..37ef8f1da 100644
--- a/Library/Homebrew/cmd/unlink.rb
+++ b/Library/Homebrew/cmd/unlink.rb
@@ -3,8 +3,10 @@ module Homebrew extend self
raise KegUnspecifiedError if ARGV.named.empty?
ARGV.kegs.each do |keg|
- print "Unlinking #{keg}... "
- puts "#{keg.unlink} links removed"
+ keg.lock do
+ print "Unlinking #{keg}... "
+ puts "#{keg.unlink} links removed"
+ end
end
end
end
diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb
index ae2dd20c4..edd5cf1d1 100644
--- a/Library/Homebrew/exceptions.rb
+++ b/Library/Homebrew/exceptions.rb
@@ -45,6 +45,18 @@ class FormulaUnavailableError < RuntimeError
end
end
+class OperationInProgressError < RuntimeError
+ def initialize name
+ message = <<-EOS.undent
+ Operation already in progress for #{name}
+ Another active Homebrew process is already using #{name}.
+ Please wait for it to finish or terminate it to continue.
+ EOS
+
+ super message
+ end
+end
+
module Homebrew
class InstallationError < RuntimeError
attr :formula
diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb
index 2e072afff..e2fde2ef9 100644
--- a/Library/Homebrew/formula.rb
+++ b/Library/Homebrew/formula.rb
@@ -228,6 +228,21 @@ class Formula
end
end
+ def lock
+ lockpath = HOMEBREW_CACHE_FORMULA/"#{@name}.brewing"
+ @lockfile = lockpath.open(File::RDWR | File::CREAT)
+ unless @lockfile.flock(File::LOCK_EX | File::LOCK_NB)
+ raise OperationInProgressError, @name
+ end
+ end
+
+ def unlock
+ unless @lockfile.nil?
+ @lockfile.flock(File::LOCK_UN)
+ @lockfile.close
+ end
+ end
+
def == b
name == b.name
end
diff --git a/Library/Homebrew/formula_installer.rb b/Library/Homebrew/formula_installer.rb
index 373440ba8..b24641e55 100644
--- a/Library/Homebrew/formula_installer.rb
+++ b/Library/Homebrew/formula_installer.rb
@@ -22,6 +22,7 @@ class FormulaInstaller
@@attempted ||= Set.new
+ lock
check_install_sanity
end
@@ -226,6 +227,8 @@ class FormulaInstaller
print "#{f.prefix}: #{f.prefix.abv}"
print ", built in #{pretty_duration build_time}" if build_time
puts
+
+ unlock if hold_locks?
end
def build_time
@@ -463,6 +466,29 @@ class FormulaInstaller
check_jars
check_non_libraries
end
+
+ private
+
+ def hold_locks?
+ @hold_locks || false
+ end
+
+ def lock
+ if (@@locked ||= []).empty?
+ f.recursive_deps.each { |d| @@locked << d } unless ignore_deps
+ @@locked.unshift(f)
+ @@locked.each(&:lock)
+ @hold_locks = true
+ end
+ end
+
+ def unlock
+ if hold_locks?
+ @@locked.each(&:unlock)
+ @@locked.clear
+ @hold_locks = false
+ end
+ end
end
diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb
index 3a4d01262..4949d24dc 100644
--- a/Library/Homebrew/keg.rb
+++ b/Library/Homebrew/keg.rb
@@ -61,6 +61,18 @@ class Keg < Pathname
parent.basename.to_s
end
+ def lock
+ path = HOMEBREW_CACHE_FORMULA/"#{fname}.brewing"
+ file = path.open(File::RDWR | File::CREAT)
+ unless file.flock(File::LOCK_EX | File::LOCK_NB)
+ raise OperationInProgressError, fname
+ end
+ yield
+ ensure
+ file.flock(File::LOCK_UN)
+ file.close
+ end
+
def linked_keg_record
@linked_keg_record ||= HOMEBREW_REPOSITORY/"Library/LinkedKegs"/fname
end