Implement locking
This commit is contained in:
parent
e5e2d2e780
commit
079bd0ee07
185
src/g.bash
185
src/g.bash
@ -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,84 +2035,71 @@ 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
|
||||
|
||||
exec $fd>"$file" || return $(g::util::false)
|
||||
printf $fd
|
||||
}
|
||||
|
||||
function g::lock::acquire() {
|
||||
local callerName="$(basename "$g__CALLER_PATH")"
|
||||
local name="${1:-$callerName}"
|
||||
local descriptor=$(g::lock::descriptor "$name")
|
||||
|
||||
flock $descriptor || g::error::throw "Unable to acquire lock: ${name} (${descriptor})"
|
||||
local lockName="$1"
|
||||
while ! g::lock::try "$lockName"; do
|
||||
sleep "$g__LOCKING_INTERVAL"
|
||||
done
|
||||
}
|
||||
|
||||
function g::lock::try() {
|
||||
local callerName="$(basename "$g__CALLER_PATH")"
|
||||
local name="${1:-$callerName}"
|
||||
local descriptor=$(g::lock::descriptor "$name")
|
||||
|
||||
g::internal "Got descriptor for lock: ${descriptor}"
|
||||
|
||||
flock -n $descriptor || return $(g::util::false)
|
||||
return $(g::util::true)
|
||||
function g::lock::holds() {
|
||||
local lockName="$1"
|
||||
return $(g::arr::includes "$lockName" "${g__LOCKING_HOLDS[@]}")
|
||||
}
|
||||
|
||||
function g::lock::release() {
|
||||
local callerName="$(basename "$g__CALLER_PATH")"
|
||||
local name="${1:-$callerName}"
|
||||
local file=$(g::lock::file "$name")
|
||||
local lockName="$1"
|
||||
|
||||
rm -rf "$file"
|
||||
if ! g::lock::holds "$lockName"; then
|
||||
g::error::throw "Cannot release lock '$lockName': not held"
|
||||
fi
|
||||
|
||||
local lockFile="$(g::path::resolve "$g__LOCKING_FILE_DIR" "${lockName}.lock")"
|
||||
rm -f "$lockFile"
|
||||
|
||||
g__LOCKING_HOLDS=("${g__LOCKING_HOLDS[@]/$lockName}")
|
||||
}
|
||||
|
||||
function g::lock::cleanupForExit() {
|
||||
for file in "${!g__LOCKING_NAME_X_DESCRIPTOR[@]}"; do
|
||||
rm -rf "$file"
|
||||
for lockFile in "${g__LOCKING_HOLDS[@]}"; do
|
||||
g::internal "Releasing lockfile: $lockFile"
|
||||
rm -f "$lockFile"
|
||||
done
|
||||
}
|
||||
|
||||
function g::lock::singleton() {
|
||||
if ! g::lock::try "$(g::lock::uniqueCallerName)"; then
|
||||
g::error::throw "Another instance of this script is already running."
|
||||
fi
|
||||
}
|
||||
|
||||
function g::lock::singleton::acquire() {
|
||||
g::lock::acquire "$(g::lock::uniqueCallerName)"
|
||||
}
|
||||
|
||||
function g::lock::uniqueCallerName() {
|
||||
g::str::replace "$g__CALLER_PATH" '/' '__'
|
||||
}
|
||||
|
||||
# dependency declarations
|
||||
|
||||
# output helpers
|
||||
@ -2079,9 +2110,5 @@ function g::lock::cleanupForExit() {
|
||||
|
||||
# sendmail support
|
||||
|
||||
# locking
|
||||
|
||||
# application definition
|
||||
|
||||
# Bootstrap the framework
|
||||
g::internals::bootstrap
|
||||
|
Loading…
Reference in New Issue
Block a user