Implement locking

master
Garrett Mills 3 years ago
parent e5e2d2e780
commit 079bd0ee07
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246

@ -66,9 +66,12 @@ g__APP_LAST_ALIAS=''
g__LOCKING_FILE_DIR="/tmp"
g__LOCKING_NAME_X_DESCRIPTOR=()
g__LOCKING_HOLDS=()
g__LOCKING_INTERVAL=0.25
g__DEBUG_EVAL=yes
g__LOCKING_NEXT_DESCRIPTOR=100
##
@ -106,6 +109,18 @@ function g::internals::cleanup() {
## ====================== UTILITIES ====================== ##
function g::eval() {
if [[ "$g__DEBUG_EVAL" == yes ]]; then
g::log::stderr "eval: $*"
fi
eval "$@"
}
function g::silence() {
"$@" > /dev/null 2>&1
}
#
# Check if an input value is numeric. (Supports decimals.)
#
@ -147,6 +162,20 @@ function g::arr::includes() {
return 1
}
function g::arr::assoc::hasKey() {
local key="$1"
shift
local array="$@"
for possibleKey in "${!array[@]}"; do
if [[ "$key" == "$possibleKey" ]]; then
return $(g::util::true)
fi
done
return $(g::util::false)
}
##
# Join an array of values by some delimiter.
#
@ -187,7 +216,7 @@ function g::util::false() {
function g::util::returnToEcho() {
local cmd="$@"
if eval "$cmd"; then
if g::eval "$cmd"; then
echo 1
else
echo 0
@ -475,7 +504,7 @@ function g::str::quote() {
function g::str::quote::eval() {
local cmd="$@"
local str="$(eval "$cmd")"
local str="$(g::eval "$cmd")"
echo "$(g::str::quote "$str")"
}
@ -612,10 +641,25 @@ function g::str::indexOf() {
fi
done
printf 'none'
return $(g::util::false)
}
function g::str::replace() {
local string="$1"
local find="$2"
local replaceWith="${3:-}"
printf "${string//${find}/${replaceWith}}"
}
function g::str::replace::once() {
local string="$1"
local find="$2"
local replaceWith="${3:-}"
printf "${string/${find}/${replaceWith}}"
}
@ -810,7 +854,7 @@ function g::log::write() {
if $g__LOGGING_TO_STDOUT; then
g::log::stdout "$output"
fi
if $g__LOGGING_TO_STDERR; then
g::log::stderr "$output"
fi
@ -1430,12 +1474,12 @@ function g::app::set() {
local resolved="$(g::app::var "$varname")"
local escaped="$(g::util::escape "$value")"
g::internal "[g::app::set] $resolved" g::app::vars
eval "export ${resolved}=${escaped}"
g::eval "export ${resolved}=${escaped}"
}
function g::app::has() {
local varname="$1"
local resolved="$(g::app::var "$varname")"
if [[ -v "$resolved" ]]; then
g::internal "[g::app::has] $resolved - YES" g::app::vars
@ -1460,7 +1504,7 @@ function g::app::command::exists() {
local cmdsVar="$(g::app::var AVAILABLE_COMMANDS)"
if g::arr::includes "$name" $(eval "echo \"\${$cmdsVar[@]}\""); then
if g::arr::includes "$name" $(g::eval "echo \"\${$cmdsVar[@]}\""); then
return $(g::util::true)
fi
@ -1482,10 +1526,10 @@ function g::app::command() {
local cmdsVar="$(g::app::var AVAILABLE_COMMANDS)"
if ! [[ -v $cmdsVar ]]; then
eval "export ${cmdsVar}=()"
g::eval "export ${cmdsVar}=()"
fi
eval "${cmdsVar}+=(\"$name\")"
g::eval "${cmdsVar}+=(\"$name\")"
g::app::set "$(g::app::command::var DESCRIPTION)" "$description"
}
@ -1843,7 +1887,7 @@ function g::app::usage() {
local commandsString=""
local cmdsVar="$(g::app::var AVAILABLE_COMMANDS)"
for cmdName in $(eval "echo \"\${$cmdsVar[@]}\""); do
for cmdName in $(g::eval "echo \"\${$cmdsVar[@]}\""); do
local cmdDescVar="$(g::app::command::var DESCRIPTION "${cmdName}")"
local cmdDescription="$(g::app::get "$cmdDescVar")"
cmdString=" ${cmdName}: ${cmdDescription}"
@ -1871,7 +1915,7 @@ function g::app::invoke() {
fi
local cmdsVar="$(g::app::var AVAILABLE_COMMANDS)"
for registeredCommandName in $(eval "echo \"\${$cmdsVar[@]}\""); do
for registeredCommandName in $(g::eval "echo \"\${$cmdsVar[@]}\""); do
if [[ "$registeredCommandName" == "$commandName" ]]; then
g::internal "Matched command: $registeredCommandName"
@ -1902,11 +1946,11 @@ function g::app::invoke() {
function g::args::buildAlias() {
local uuid="$1"
alias "g::args::alias::${uuid}"="g::args "$uuid""
alias "g::args::alias::${uuid}::arg"="g::arg "$uuid""
alias "g::args::alias::${uuid}::arg::has"="g::arg:has "$uuid""
alias "g::args::alias::${uuid}::flag"="g::flag "$uuid""
alias "g::args::alias::${uuid}::flag::has"="g::flag:has "$uuid""
alias g::args::alias::${uuid}="g::args \"$uuid\""
alias g::args::alias::${uuid}::arg="g::arg \"$uuid\""
alias g::args::alias::${uuid}::arg::has="g::arg:has \"$uuid\""
alias g::args::alias::${uuid}::flag="g::flag \"$uuid\""
alias g::args::alias::${uuid}::flag::has="g::flag:has \"$uuid\""
g__APP_LAST_ALIAS="g::args::alias::${uuid}"
}
@ -1991,82 +2035,69 @@ function g::flag::has() {
}
g::log::all # TODO remove
function g::lock::file() {
local callerName="$(basename "$g__CALLER_PATH")"
local name="${1:-$callerName}"
g::internal "Resolving lock file path for name: $name" g::lock
g::path::resolve "$g__LOCKING_FILE_DIR" "${name}.lock"
}
function g::lock::try() {
local lockName="$1"
function g::arr::assoc::hasKey() {
local key="$1"
shift
local array="$@"
if g::lock::holds "$lockName"; then
return $(g::util::true)
fi
for possibleKey in "${!array[@]}"; do
if [[ "$key" == "$possibleKey" ]]; then
return $(g::util::true)
fi
done
local lockFile="$(g::path::resolve "$g__LOCKING_FILE_DIR" "${lockName}.lock")"
local tempFile="$(mktemp --suffix .lock)"
if g::silence ln "$tempFile" "$lockFile"; then
g__LOCKING_HOLDS+=("$lockName")
return $(g::util::true)
fi
return $(g::util::false)
}
function g::lock::descriptor() {
local callerName="$(basename "$g__CALLER_PATH")"
local name="${1:-$callerName}"
local file="$(g::lock::file "$name")"
g::internal "Resolving lock file descriptor for file: $file" g::lock
if g::arr::assoc::hasKey "$name" "${g__LOCKING_NAME_X_DESCRIPTOR[@]}"; then
printf $fd
return $(g::util::true)
fi
local fd=$g__LOCKING_NEXT_DESCRIPTOR
g__LOCKING_NEXT_DESCRIPTOR=$(($g__LOCKING_NEXT_DESCRIPTOR + 1))
g__LOCKING_NAME_X_DESCRIPTOR+=( ["$name"]=$fd )
g::internal "Mapped FD $fd to lock file: $file" g::lock
function g::lock::acquire() {
local lockName="$1"
while ! g::lock::try "$lockName"; do
sleep "$g__LOCKING_INTERVAL"
done
}
exec $fd>"$file" || return $(g::util::false)
printf $fd
function g::lock::holds() {
local lockName="$1"
return $(g::arr::includes "$lockName" "${g__LOCKING_HOLDS[@]}")
}
function g::lock::acquire() {
local callerName="$(basename "$g__CALLER_PATH")"
local name="${1:-$callerName}"
local descriptor=$(g::lock::descriptor "$name")
function g::lock::release() {
local lockName="$1"
flock $descriptor || g::error::throw "Unable to acquire lock: ${name} (${descriptor})"
}
if ! g::lock::holds "$lockName"; then
g::error::throw "Cannot release lock '$lockName': not held"
fi
function g::lock::try() {
local callerName="$(basename "$g__CALLER_PATH")"
local name="${1:-$callerName}"
local descriptor=$(g::lock::descriptor "$name")
local lockFile="$(g::path::resolve "$g__LOCKING_FILE_DIR" "${lockName}.lock")"
rm -f "$lockFile"
g::internal "Got descriptor for lock: ${descriptor}"
g__LOCKING_HOLDS=("${g__LOCKING_HOLDS[@]/$lockName}")
}
flock -n $descriptor || return $(g::util::false)
return $(g::util::true)
function g::lock::cleanupForExit() {
for lockFile in "${g__LOCKING_HOLDS[@]}"; do
g::internal "Releasing lockfile: $lockFile"
rm -f "$lockFile"
done
}
function g::lock::release() {
local callerName="$(basename "$g__CALLER_PATH")"
local name="${1:-$callerName}"
local file=$(g::lock::file "$name")
function g::lock::singleton() {
if ! g::lock::try "$(g::lock::uniqueCallerName)"; then
g::error::throw "Another instance of this script is already running."
fi
}
rm -rf "$file"
function g::lock::singleton::acquire() {
g::lock::acquire "$(g::lock::uniqueCallerName)"
}
function g::lock::cleanupForExit() {
for file in "${!g__LOCKING_NAME_X_DESCRIPTOR[@]}"; do
rm -rf "$file"
done
function g::lock::uniqueCallerName() {
g::str::replace "$g__CALLER_PATH" '/' '__'
}
# dependency declarations
@ -2079,9 +2110,5 @@ function g::lock::cleanupForExit() {
# sendmail support
# locking
# application definition
# Bootstrap the framework
g::internals::bootstrap

Loading…
Cancel
Save