mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-15 19:57:07 +08:00
fix: 修复任务生命周期与调度就绪,优化执行和构建开销 (#3069)
* fix: harden task lifecycle and scheduler readiness * fix: confine log writes to the configured log directory * fix: verify complete build artifacts and untracked inputs * fix: reconcile scheduler state and make stop win startup races * fix: isolate cron generations and serialize scheduler recovery
This commit is contained in:
+34
-13
@@ -10,16 +10,39 @@ create_token() {
|
||||
}
|
||||
|
||||
get_token() {
|
||||
if [[ -f $file_auth_token ]]; then
|
||||
__ql_token__=$(cat $file_auth_token | jq -r .value)
|
||||
local expiration=$(cat $file_auth_token | jq -r .expiration)
|
||||
local currentTimeStamp=$(date +%s)
|
||||
if [[ $currentTimeStamp -ge $expiration ]]; then
|
||||
create_token
|
||||
local token_data expiration token_value
|
||||
local currentTimeStamp=${EPOCHSECONDS:-$(date +%s)}
|
||||
if [[ -f "$file_auth_token" ]] && token_data=$(jq -er \
|
||||
'select((.expiration | type) == "number" and (.value | type) == "string" and (.value | length) > 0 and (.value | test("[\r\n]") | not)) | .expiration, .value' \
|
||||
"$file_auth_token" 2>/dev/null); then
|
||||
expiration=${token_data%%$'\n'*}
|
||||
token_value=${token_data#*$'\n'}
|
||||
if [[ "$expiration" =~ ^[1-9][0-9]{0,10}$ && -n "$token_value" && "$token_value" != *$'\n'* && "$token_value" != *$'\r'* ]] && \
|
||||
(( currentTimeStamp < expiration )); then
|
||||
__ql_token__=$token_value
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
create_token
|
||||
fi
|
||||
create_token
|
||||
}
|
||||
|
||||
# Read the status response once; preserve multiline error messages and the
|
||||
# legacy code/message variables used by the shell API callers.
|
||||
ql_parse_status_response() {
|
||||
# Both task status and statistics endpoints normally return this exact body.
|
||||
# Match the whole document; all other JSON and malformed responses still
|
||||
# use jq. Keep its missing-message result for existing callers.
|
||||
if [[ "$1" == '{"code":200}' ]]; then
|
||||
code=200
|
||||
message=null
|
||||
return 0
|
||||
fi
|
||||
local parsed
|
||||
code=""
|
||||
message=""
|
||||
parsed=$(jq -r '.code, .message' <<< "$1") || return $?
|
||||
code=${parsed%%$'\n'*}
|
||||
message=${parsed#*$'\n'}
|
||||
}
|
||||
|
||||
add_cron_api() {
|
||||
@@ -142,7 +165,7 @@ update_cron() {
|
||||
local lastExecutingTime="${5:-0}"
|
||||
local runningTime="${6:-0}"
|
||||
local exitCode="${7:-}"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local currentTimeStamp=${EPOCHSECONDS:-$(date +%s)}
|
||||
local dataRaw="{\"ids\":[$ids],\"status\":\"$status\",\"pid\":\"$pid\",\"log_path\":\"$logPath\",\"last_execution_time\":$lastExecutingTime,\"last_running_time\":$runningTime"
|
||||
if [[ -n $exitCode ]]; then
|
||||
dataRaw="${dataRaw},\"exit_code\":$exitCode"
|
||||
@@ -156,8 +179,7 @@ update_cron() {
|
||||
--data-raw "$dataRaw" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo "$api" | jq -r .code)
|
||||
message=$(echo "$api" | jq -r .message)
|
||||
ql_parse_status_response "$api" || true
|
||||
if [[ $code != 200 ]]; then
|
||||
if [[ ! $message ]]; then
|
||||
message="$api"
|
||||
@@ -240,8 +262,7 @@ record_cron_stat() {
|
||||
--data-raw "{\"ref_id\":$ref_id,\"code\":$exit_code,\"elapsed\":$elapsed}" \
|
||||
--compressed
|
||||
)
|
||||
code=$(echo "$api" | jq -r .code)
|
||||
message=$(echo "$api" | jq -r .message)
|
||||
ql_parse_status_response "$api" || true
|
||||
if [[ $code != 200 ]]; then
|
||||
if [[ ! $message ]]; then
|
||||
message="$api"
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/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
|
||||
# Bound the wait; absent/unsupported flock or contention falls back to
|
||||
# independent discovery. Never remove the lock file while waiters exist.
|
||||
if flock -w 2 9 2>/dev/null; then
|
||||
if ql_read_node_path_cache "$cache" "$key"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
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"
|
||||
}
|
||||
+7
-1
@@ -95,7 +95,13 @@ append_node_dependency_path() {
|
||||
# 用户依赖目录加入 NODE_PATH,替代 symlink 到 node_modules 的方式
|
||||
export NODE_PATH="${NODE_PATH:+${NODE_PATH}:}${dir_dep}"
|
||||
|
||||
local pnpm_global_path=$(pnpm root -g 2>/dev/null)
|
||||
local pnpm_global_path
|
||||
if [[ -f "$dir_shell/node_path_cache.sh" ]]; then
|
||||
. "$dir_shell/node_path_cache.sh"
|
||||
pnpm_global_path=$(ql_get_node_global_path) || true
|
||||
else
|
||||
pnpm_global_path=$(pnpm root -g 2>/dev/null) || true
|
||||
fi
|
||||
if [[ -n "$pnpm_global_path" ]]; then
|
||||
export QL_NODE_GLOBAL_PATH="$pnpm_global_path"
|
||||
export NODE_PATH="${NODE_PATH:+${NODE_PATH}:}${pnpm_global_path}"
|
||||
|
||||
+18
-6
@@ -368,9 +368,16 @@ format_timestamp() {
|
||||
|
||||
get_env_array() {
|
||||
exported_variables=()
|
||||
while IFS= read -r line; do
|
||||
exported_variables+=("$line")
|
||||
done < <(grep '^export ' $file_env | awk '{print $2}' | cut -d= -f1)
|
||||
# Preserve the legacy export-line/second-field rules without evaluating values.
|
||||
local export_name_program='/^export / { name = $2; sub(/=.*/, "", name); print name }'
|
||||
if [[ ${BASH_VERSINFO[0]} -ge 4 ]]; then
|
||||
builtin mapfile -t exported_variables < <(awk "$export_name_program" "$file_env")
|
||||
else
|
||||
# macOS still ships Bash 3, which does not provide mapfile.
|
||||
while IFS= read -r line; do
|
||||
exported_variables+=("$line")
|
||||
done < <(awk "$export_name_program" "$file_env")
|
||||
fi
|
||||
}
|
||||
|
||||
clear_env() {
|
||||
@@ -411,9 +418,14 @@ run_task_after() {
|
||||
}
|
||||
|
||||
handle_task_end() {
|
||||
local etime=$(date "+$time_format")
|
||||
local end_time=$(format_time "$time_format" "$etime")
|
||||
local end_timestamp=$(format_timestamp "$time_format" "$etime")
|
||||
local etime end_time end_timestamp
|
||||
if [[ $is_macos -ne 1 && $time_format == '%Y-%m-%d %H:%M:%S' ]]; then
|
||||
IFS='|' read -r end_time end_timestamp < <(date "+$time_format|%s")
|
||||
else
|
||||
etime=$(date "+$time_format")
|
||||
end_time=$(format_time "$time_format" "$etime")
|
||||
end_timestamp=$(format_timestamp "$time_format" "$etime")
|
||||
fi
|
||||
local diff_time=$(($end_timestamp - $begin_timestamp))
|
||||
local exit_code="${_task_exit_code:-0}"
|
||||
[[ "$diff_time" == 0 ]] && diff_time=1
|
||||
|
||||
+13
-3
@@ -52,8 +52,14 @@ handle_log_path() {
|
||||
fi
|
||||
fi
|
||||
|
||||
time=$(date "+$mtime_format")
|
||||
log_time=$(format_log_time "$mtime_format" "$time")
|
||||
if [[ $is_macos -ne 1 && $mtime_format == '%Y-%m-%d %H:%M:%S.%3N' ]]; then
|
||||
# Render both representations from one clock snapshot, without parsing it
|
||||
# again in a second date process.
|
||||
IFS='|' read -r time log_time < <(date "+$mtime_format|%Y-%m-%d-%H-%M-%S-%3N")
|
||||
else
|
||||
time=$(date "+$mtime_format")
|
||||
log_time=$(format_log_time "$mtime_format" "$time")
|
||||
fi
|
||||
if [[ -z $log_name ]]; then
|
||||
log_dir_tmp="${file_param##*/}"
|
||||
if [[ $file_param =~ "/" ]]; then
|
||||
@@ -124,7 +130,11 @@ format_params() {
|
||||
}
|
||||
|
||||
init_begin_time() {
|
||||
begin_time=$(format_time "$time_format" "$time")
|
||||
if [[ $is_macos -ne 1 && $mtime_format == '%Y-%m-%d %H:%M:%S.%3N' && $time_format == '%Y-%m-%d %H:%M:%S' ]]; then
|
||||
begin_time=${time%.*}
|
||||
else
|
||||
begin_time=$(format_time "$time_format" "$time")
|
||||
fi
|
||||
begin_timestamp=$(format_timestamp "$time_format" "$time")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user