61 lines
2.1 KiB
Ruby
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
|