Add new middleware builtin: Lock
This will do a process lock by flocking a file. If it fails, it will raise an exception of choice.
This commit is contained in:
parent
eed24a08e3
commit
9ae3a373c8
|
@ -15,6 +15,7 @@ module Vagrant
|
|||
autoload :ConfigValidate, "vagrant/action/builtin/config_validate"
|
||||
autoload :EnvSet, "vagrant/action/builtin/env_set"
|
||||
autoload :GracefulHalt, "vagrant/action/builtin/graceful_halt"
|
||||
autoload :Lock, "vagrant/action/builtin/lock"
|
||||
autoload :Provision, "vagrant/action/builtin/provision"
|
||||
autoload :SSHExec, "vagrant/action/builtin/ssh_exec"
|
||||
autoload :SSHRun, "vagrant/action/builtin/ssh_run"
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
module Vagrant
|
||||
module Action
|
||||
module Builtin
|
||||
# This class creates a multi-process lock using `flock`. The lock
|
||||
# is active for the remainder of the middleware stack.
|
||||
class Lock
|
||||
def initialize(app, env, options=nil)
|
||||
@app = app
|
||||
@options ||= options || {}
|
||||
raise ArgumentError, "Please specify a lock path" if !@options[:path]
|
||||
raise ArgumentError, "Please specify an exception." if !@options[:exception]
|
||||
end
|
||||
|
||||
def call(env)
|
||||
lock_path = @options[:path]
|
||||
env_key = "has_lock_#{lock_path}"
|
||||
|
||||
if !env[env_key]
|
||||
# If we already have the key in our environment we assume the
|
||||
# lock is held by our middleware stack already and we allow
|
||||
# nesting.
|
||||
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.
|
||||
if f.flock(File::LOCK_EX | File::LOCK_NB) === false
|
||||
raise @options[:exception]
|
||||
end
|
||||
|
||||
# Set that we gained the lock and call deeper into the
|
||||
# middleware, but make sure we UNSET the lock when we leave.
|
||||
begin
|
||||
env[env_key] = true
|
||||
@app.call(env)
|
||||
ensure
|
||||
env[env_key] = false
|
||||
end
|
||||
end
|
||||
else
|
||||
# Just call up the middleware because we already hold the lock
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,67 @@
|
|||
require File.expand_path("../../../../base", __FILE__)
|
||||
|
||||
describe Vagrant::Action::Builtin::Lock do
|
||||
let(:app) { lambda { |env| } }
|
||||
let(:env) { {} }
|
||||
let(:lock_path) do
|
||||
@__lock_path = Tempfile.new("vagrant-test-lock")
|
||||
@__lock_path.path.to_s
|
||||
end
|
||||
|
||||
let(:options) do
|
||||
{
|
||||
:exception => Class.new(StandardError),
|
||||
:path => lock_path
|
||||
}
|
||||
end
|
||||
|
||||
it "should require a path" do
|
||||
expect { described_class.new(app, env) }.
|
||||
to raise_error(ArgumentError)
|
||||
|
||||
expect { described_class.new(app, env, :path => "foo") }.
|
||||
to raise_error(ArgumentError)
|
||||
|
||||
expect { described_class.new(app, env, :exception => "foo") }.
|
||||
to raise_error(ArgumentError)
|
||||
|
||||
expect { described_class.new(app, env, :path => "bar", :exception => "foo") }.
|
||||
to_not raise_error
|
||||
end
|
||||
|
||||
it "should call the middleware with the lock held" do
|
||||
inner_acquire = true
|
||||
app = lambda do |env|
|
||||
File.open(lock_path, "w+") do |f|
|
||||
inner_acquire = f.flock(File::LOCK_EX | File::LOCK_NB)
|
||||
end
|
||||
end
|
||||
|
||||
instance = described_class.new(app, env, options)
|
||||
instance.call(env)
|
||||
|
||||
inner_acquire.should == false
|
||||
end
|
||||
|
||||
it "should raise an exception if the lock is already held" do
|
||||
File.open(lock_path, "w+") do |f|
|
||||
# Acquire lock
|
||||
f.flock(File::LOCK_EX | File::LOCK_NB).should == 0
|
||||
|
||||
# Test!
|
||||
instance = described_class.new(app, env, options)
|
||||
expect { instance.call(env) }.
|
||||
to raise_error(options[:exception])
|
||||
end
|
||||
end
|
||||
|
||||
it "should allow nesting locks within the same middleware sequence" do
|
||||
called = false
|
||||
app = lambda { |env| called = true }
|
||||
inner = described_class.new(app, env, options)
|
||||
outer = described_class.new(inner, env, options)
|
||||
outer.call(env)
|
||||
|
||||
called.should == true
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue