Only one copy of Vagrant can run at any given time. [closes GH-364]

This is to protect against issues with VirtualBox overwriting
each other.
This commit is contained in:
Mitchell Hashimoto 2011-07-09 15:57:24 -07:00
parent 79a2ffbf73
commit 874a9763f5
6 changed files with 112 additions and 2 deletions

View File

@ -25,6 +25,7 @@
- Multiple Chef provisioners no longer overwrite cookbook folders. [GH-407]
- `package` won't delete previously existing file. [GH-408]
- Vagrantfile can be lowercase now. [GH-399]
- Only one copy of Vagrant may be running at any given time. [GH-364]
## 0.7.6 (July 2, 2011)

View File

@ -128,7 +128,10 @@ module Vagrant
@@reported_interrupt = true
end
Busy.busy(int_callback) { callable.call(action_environment) }
# We place a process lock around every action that is called
env.lock do
Busy.busy(int_callback) { callable.call(action_environment) }
end
end
end
end

View File

@ -60,7 +60,8 @@ module Vagrant
:parent => nil,
:vm => nil,
:cwd => nil,
:vagrantfile_name => nil
:vagrantfile_name => nil,
:lock_path => nil
}.merge(opts || {})
# Set the default working directory to look for the vagrantfile
@ -77,6 +78,7 @@ module Vagrant
end
@loaded = false
@lock_acquired = false
end
#---------------------------------------------------------------
@ -281,6 +283,39 @@ module Vagrant
@root_path = root_finder.call(cwd)
end
# This returns the path which Vagrant uses to determine the location
# of the file lock. This is specific to each operating system.
def lock_path
@lock_path || tmp_path.join("vagrant.lock")
end
# This locks Vagrant for the duration of the block passed to this
# method. During this time, any other environment which attempts
# to lock which points to the same lock file will fail.
def lock
# This allows multiple locks in the same process to be nested
return yield if @lock_acquired
File.open(lock_path, "w+") do |f|
# The file locking fails only if it returns "false." If it
# succeeds it returns a 0, so we must explicitly check for
# the proper error case.
raise Errors::EnvironmentLockedError if f.flock(File::LOCK_EX | File::LOCK_NB) === false
begin
# Mark that we have a lock
@lock_acquired = true
yield
ensure
# We need to make sure that no matter what this is always
# reset to false so we don't think we have a lock when we
# actually don't.
@lock_acquired = false
end
end
end
#---------------------------------------------------------------
# Config Methods
#---------------------------------------------------------------

View File

@ -153,6 +153,11 @@ module Vagrant
error_key(:status_error, "vagrant.downloaders.http")
end
class EnvironmentLockedError < VagrantError
status_code(52)
error_key(:environment_locked)
end
class ForwardPortAutolistEmpty < VagrantError
status_code(27)
error_key(:auto_empty, "vagrant.actions.vm.forward_ports")

View File

@ -31,6 +31,11 @@ en:
this command in another directory. If you aren't in a home directory,
then please rename ".vagrant" to something else, or configure Vagrant
to use another filename by modifying `config.vagrant.dotfile_name`.
environment_locked: |-
An instance of Vagrant is already running. Only one instance of Vagrant
may run at any given time to avoid problems with VirtualBox inconsistencies
occurring. Please wait for the other instance of Vagrant to end and then
try again.
interrupted: "Vagrant exited after cleanup due to external interrupt."
multi_vm_required: "A multi-vm environment is required for name specification to this command."
multi_vm_target_required: "`vagrant %{command}` requires a specific VM name to target in a multi-VM environment."

View File

@ -1,5 +1,6 @@
require "test_helper"
require "pathname"
require "tempfile"
class EnvironmentTest < Test::Unit::TestCase
setup do
@ -291,6 +292,66 @@ class EnvironmentTest < Test::Unit::TestCase
end
end
context "locking" do
setup do
@instance = @klass.new(:lock_path => Tempfile.new('vagrant-test').path)
end
should "allow nesting locks" do
assert_nothing_raised do
@instance.lock do
@instance.lock do
# Nothing
end
end
end
end
should "raise an exception if an environment already has a lock" do
@another = @klass.new(:lock_path => @instance.lock_path)
# Create first locked thread which should succeed
first = Thread.new do
begin
@instance.lock do
Thread.current[:locked] = true
loop { sleep 1000 }
end
rescue Vagrant::Errors::EnvironmentLockedError
Thread.current[:locked] = false
end
end
# Wait until the first thread has acquired the lock
loop do
break if first[:locked] || !first.alive?
Thread.pass
end
# Verify that the fist got the lock
assert first[:locked]
# Create second locked thread which should fail
second = Thread.new do
begin
@another.lock do
Thread.current[:error] = false
end
rescue Vagrant::Errors::EnvironmentLockedError
Thread.current[:error] = true
end
end
# Wait for the second to end and verify it failed
second.join
assert second[:error]
# Make sure both threads are killed
first.kill
second.kill
end
end
context "accessing the configuration" do
should "load the environment if its not already loaded" do
env = @klass.new(:cwd => vagrantfile)