Files
ljwx-deploy/scripts/github/_gh_retry.sh
T

161 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
gh_retry_temp_failure_rc() {
printf '%s' "${GH_RETRY_TEMP_FAILURE_RC:-75}"
}
gh_retry_timeout_cmd() {
if command -v timeout >/dev/null 2>&1; then
printf '%s\n' "timeout"
return 0
fi
if command -v gtimeout >/dev/null 2>&1; then
printf '%s\n' "gtimeout"
return 0
fi
return 1
}
gh_retry_is_temp_failure_rc() {
local rc="${1:-0}"
[[ "$rc" -eq "$(gh_retry_temp_failure_rc)" ]]
}
gh_retry_compact_message() {
local message="$1"
message="${message//$'\r'/ }"
message="$(printf '%s' "$message" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
if [[ -z "$message" ]]; then
message="unknown GitHub API error"
fi
printf '%s' "$message"
}
gh_retry_is_transient_error() {
local lowered
lowered="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
case "$lowered" in
*"could not resolve host"*|\
*"temporary failure in name resolution"*|\
*"name or service not known"*|\
*"no such host"*|\
*"server misbehaving"*|\
*"error connecting to api.github.com"*|\
*"dial tcp"*|\
*"i/o timeout"*|\
*"timed out"*|\
*"timed out after"*|\
*"command timed out"*|\
*"timeout awaiting response headers"*|\
*"tls handshake timeout"*|\
*"connection reset"*|\
*"connection refused"*|\
*"connection aborted"*|\
*"context deadline exceeded"*|\
*"secondary rate limit"*|\
*"rate limit exceeded"*|\
*"retry-after"*|\
*"econnreset"*|\
*"http 429"*|\
*"http 502"*|\
*"http 503"*|\
*"http 504"*|\
*"bad gateway"*|\
*"service unavailable"*|\
*"gateway timeout"*)
return 0
;;
esac
return 1
}
gh_retry_execute() {
local timeout_sec="${GH_RETRY_COMMAND_TIMEOUT_SEC:-60}"
local timeout_bin=""
if timeout_bin="$(gh_retry_timeout_cmd)"; then
"$timeout_bin" "$timeout_sec" "$@"
return $?
fi
"$@"
}
gh_retry_command() {
local label="$1"
shift
local max_attempts="${GH_RETRY_MAX_ATTEMPTS:-4}"
local base_delay="${GH_RETRY_BASE_DELAY_SEC:-2}"
local max_delay="${GH_RETRY_MAX_DELAY_SEC:-15}"
local temp_rc
temp_rc="$(gh_retry_temp_failure_rc)"
local stdout_file=""
local stderr_file=""
stdout_file="$(mktemp)"
stderr_file="$(mktemp)"
local attempt=1
local delay="$base_delay"
local rc=0
local stderr_text=""
local summary=""
local timeout_sec="${GH_RETRY_COMMAND_TIMEOUT_SEC:-60}"
while (( attempt <= max_attempts )); do
if gh_retry_execute "$@" >"$stdout_file" 2>"$stderr_file"; then
cat "$stdout_file"
rm -f "$stdout_file" "$stderr_file"
return 0
fi
rc=$?
stderr_text="$(cat "$stderr_file")"
if [[ "$rc" -eq 124 || "$rc" -eq 137 ]]; then
stderr_text="${stderr_text:-command timed out after ${timeout_sec}s}"
fi
if ! gh_retry_is_transient_error "$stderr_text"; then
if [[ -n "$stderr_text" ]]; then
printf '%s\n' "$stderr_text" >&2
fi
rm -f "$stdout_file" "$stderr_file"
return "$rc"
fi
summary="$(gh_retry_compact_message "$stderr_text")"
if (( attempt == max_attempts )); then
printf '[错误] GitHub 临时连接失败,%s 已重试 %s 次: %s\n' "$label" "$max_attempts" "$summary" >&2
rm -f "$stdout_file" "$stderr_file"
return "$temp_rc"
fi
printf '[警告] GitHub 临时连接失败,%s 第 %s/%s 次失败,%s 秒后重试: %s\n' \
"$label" "$attempt" "$max_attempts" "$delay" "$summary" >&2
sleep "$delay"
attempt=$((attempt + 1))
delay=$((delay * 2))
if (( delay > max_delay )); then
delay="$max_delay"
fi
done
rm -f "$stdout_file" "$stderr_file"
return "$temp_rc"
}
gh_api_retry() {
local label="$1"
shift
gh_retry_command "$label" gh api "$@"
}
gh_graphql_retry() {
local label="$1"
shift
gh_retry_command "$label" gh api graphql "$@"
}