161 lines
3.8 KiB
Bash
Executable File
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 "$@"
|
|
}
|