61 lines
2.1 KiB
Ruby

# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
require "log4r"
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
@logger = Log4r::Logger.new("vagrant::action::builtin::lock")
@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]
lock_path = lock_path.call(env) if lock_path.is_a?(Proc)
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.
@logger.info("Locking: #{lock_path}")
if f.flock(File::LOCK_EX | File::LOCK_NB) === false
exception = @options[:exception]
exception = exception.call(env) if exception.is_a?(Proc)
raise 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
@logger.info("Unlocking: #{lock_path}")
env[env_key] = false
f.flock(File::LOCK_UN)
end
end
else
# Just call up the middleware because we already hold the lock
@app.call(env)
end
end
end
end
end
end