Files
qinglong/shell/node_path_cache.sh
T
whyour 4eb27427f8 fix: 修复 develop 调度锁、运行恢复与 Alpine 缓存并发 (#3070)
* fix: repair develop scheduler locks and runtime recovery

* test: wait for child reaping after verified termination

* fix: abort cron mutations when scheduler deletion fails
2026-09-13 13:44:44 +08:00

129 lines
4.3 KiB
Bash

#!/usr/bin/env bash
# Cache discovery only, never the installed packages. Ordinary installs at the
# same root are visible immediately. TTL bounds changes outside known inputs.
ql_node_path_cache_key() {
local pnpm_bin node_bin name directory file
pnpm_bin=$(type -P pnpm) || return 1
node_bin=$(type -P node) || return 1
{
printf '%s\n' 'v1' "$EUID" "$PWD" "$PATH" "${HOME:-}" \
"${PNPM_HOME:-}" "${XDG_CONFIG_HOME:-}" "${XDG_DATA_HOME:-}" \
"$pnpm_bin" "$node_bin"
for name in ${!npm_config_@} ${!NPM_CONFIG_@}; do
printf '%s=%s\n' "$name" "${!name}"
done
# Follow executable symlinks, detecting upgrades at the same command path.
stat -Lc '%n:%i:%s:%Y:%Z' "$pnpm_bin" "$node_bin" 2>/dev/null || \
stat -Lf '%N:%i:%z:%m:%c' "$pnpm_bin" "$node_bin" 2>/dev/null
directory=$PWD
while :; do
for file in "$directory/.npmrc" "$directory/pnpm-workspace.yaml"; do
if [[ -f "$file" ]]; then
printf '%s\n' "$file"
cat -- "$file"
printf '\n'
fi
done
[[ "$directory" == / ]] && break
directory=${directory%/*}
[[ -n "$directory" ]] || directory=/
done
for file in "${HOME:-}/.npmrc" \
"${XDG_CONFIG_HOME:-${HOME:-}/.config}/pnpm/rc" \
"${npm_config_userconfig:-${NPM_CONFIG_USERCONFIG:-/dev/null}}" \
"${npm_config_globalconfig:-${NPM_CONFIG_GLOBALCONFIG:-/dev/null}}"; do
if [[ -f "$file" ]]; then
printf '%s\n' "$file"
cat -- "$file"
printf '\n'
fi
done
} | cksum
}
ql_read_node_path_cache() {
local cache="$1" key="$2" now
local stored_key="" stored_at="" stored_path=""
now=${EPOCHSECONDS:-$(date +%s)}
if [[ -f "$cache" && ! -L "$cache" && -O "$cache" ]]; then
if ! {
IFS= read -r stored_key && IFS= read -r stored_at && IFS= read -r stored_path
} < "$cache"; then
return 1
fi
if [[ "$stored_key" == "$key" && "$stored_at" =~ ^(0|[1-9][0-9]{0,10})$ && "$stored_path" == /* ]] && \
(( now >= stored_at && now - stored_at < 60 )); then
printf '%s\n' "$stored_path"
return 0
fi
fi
return 1
}
# Keep the lock descriptor in a subshell so sourcing this helper never changes
# the caller's descriptors. A crashed refresher releases the kernel lock.
ql_refresh_node_global_path() (
local cache="$1" key="$2" now result previous_umask
local lock="${cache}.lock"
previous_umask=$(umask)
umask 077
if type -P flock &>/dev/null && mkdir -p -- "$dir_tmp" 2>/dev/null; then
if [[ ! -L "$lock" && ( ! -e "$lock" || ( -f "$lock" && -O "$lock" ) ) ]] && \
{ exec 9>> "$lock"; } 2>/dev/null; then
# BusyBox flock (Alpine) has no -w. Both implementations support -n;
# bound contention retries and fall back immediately on other failures.
# Never remove the lock file while waiters exist.
local lock_attempt=0 lock_status
while :; do
if flock -n 9 2>/dev/null; then
if ql_read_node_path_cache "$cache" "$key"; then
return 0
fi
break
else
lock_status=$?
fi
[[ "$lock_status" == 1 && "$lock_attempt" -lt 20 ]] || break
lock_attempt=$((lock_attempt + 1))
sleep 0.1
done
fi
fi
# Private lock creation must not change pnpm's inherited creation mask.
umask "$previous_umask"
now=${EPOCHSECONDS:-$(date +%s)}
result=$(pnpm root -g 9>&- 2>/dev/null) || return $?
# Never cache failed, empty, multiline or non-absolute answers.
if [[ "$result" == /* && "$result" != *$'\n'* && "$result" != *$'\r'* ]]; then
(
umask 077
mkdir -p -- "$dir_tmp" || exit 0
local temporary
temporary=$(mktemp "${cache}.XXXXXX") || exit 0
if printf '%s\n%s\n%s\n' "$key" "$now" "$result" > "$temporary"; then
mv -f -- "$temporary" "$cache" || rm -f -- "$temporary"
else
rm -f -- "$temporary"
fi
) 2>/dev/null
fi
printf '%s\n' "$result"
)
ql_get_node_global_path() {
if [[ "${QL_NODE_PATH_CACHE:-1}" == 0 ]]; then
pnpm root -g 2>/dev/null
return $?
fi
local key cache
cache="${dir_tmp}/pnpm-root-${EUID}.cache"
key=$(ql_node_path_cache_key) || { pnpm root -g 2>/dev/null; return $?; }
if ql_read_node_path_cache "$cache" "$key"; then
return 0
fi
ql_refresh_node_global_path "$cache" "$key"
}