diff --git a/src/g.bash b/src/g.bash index c0ce26a..99bb642 100644 --- a/src/g.bash +++ b/src/g.bash @@ -12,7 +12,7 @@ export PS4='+(${BASH_SOURCE##*/}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' export g__EXISTS=true g__PATH="${BASH_SOURCE[0]}" -g__CALLER_PATH="${BASH_SOURCE[1]}" +g__CALLER_PATH="${BASH_SOURCE[1]:-shell}" # The global logging level. Lower the number, higher the priority. g__LOGGING_LEVEL=2 @@ -64,6 +64,12 @@ g__APP_LAST_ARGPARSE='' g__APP_LAST_ALIAS='' +g__LOCKING_FILE_DIR="/tmp" + +g__LOCKING_NAME_X_DESCRIPTOR=() + +g__LOCKING_NEXT_DESCRIPTOR=100 + ## # Initialize the framework @@ -92,6 +98,8 @@ function g::internals::cleanup() { for file in "${g__FILES_TO_CLEANUP[@]}"; do rm -rf "$file" done + + g::lock::cleanupForExit } @@ -1982,14 +1990,89 @@ function g::flag::has() { return $(g::app::command::parse::has::flag "$nameOrPosition" "$uuid") } -# logging/debug flags + +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::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) +} + +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})" +} + +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::release() { + local callerName="$(basename "$g__CALLER_PATH")" + local name="${1:-$callerName}" + local file=$(g::lock::file "$name") + + rm -rf "$file" +} + +function g::lock::cleanupForExit() { + for file in "${!g__LOCKING_NAME_X_DESCRIPTOR[@]}"; do + rm -rf "$file" + done +} # dependency declarations # output helpers -# argument parsing - # exception handling # JSON parsing, INI parsing, rich data types (named parameters & arrays/maps)