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.
This commit is contained in:
parent
ea4188ecda
commit
37a56fa513
@ -35,11 +35,13 @@ module Homebrew extend self
|
||||
next
|
||||
end
|
||||
|
||||
keg.lock do
|
||||
print "Linking #{keg}... " do
|
||||
puts "#{keg.link(mode)} symlinks created"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
@ -7,11 +7,13 @@ module Homebrew extend self
|
||||
|
||||
if not ARGV.force?
|
||||
ARGV.kegs.each do |keg|
|
||||
keg.lock do
|
||||
puts "Uninstalling #{keg}..."
|
||||
keg.unlink
|
||||
keg.uninstall
|
||||
rm_opt_link keg.fname
|
||||
end
|
||||
end
|
||||
else
|
||||
ARGV.named.each do |name|
|
||||
name = Formula.canonical_name(name)
|
||||
|
@ -3,8 +3,10 @@ module Homebrew extend self
|
||||
raise KegUnspecifiedError if ARGV.named.empty?
|
||||
|
||||
ARGV.kegs.each do |keg|
|
||||
keg.lock do
|
||||
print "Unlinking #{keg}... "
|
||||
puts "#{keg.unlink} links removed"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user