Files
actions/retry/action.yml
T
2025-05-11 21:19:14 +02:00

159 lines
5.1 KiB
YAML

# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
# Example usage:
#
# Basic usage (will retry failed step 3 times):
# steps:
# - uses: ultralytics/actions/retry@main
# with:
# run: python train.py
#
# Advanced usage:
# steps:
# - uses: ultralytics/actions/retry@main
# with:
# run: |
# python setup.py install
# pytest tests/
# retries: 2 # Retry twice after initial attempt (3 total runs)
# timeout_minutes: 30 # Each attempt times out after 30 minutes
# retry_delay_seconds: 60 # Wait 60 seconds between retries
# shell: bash # Use python or bash shell
name: "Step-Level Retry"
description: "Retries a step while preserving its full context"
inputs:
timeout_minutes:
description: "Maximum time in minutes for each attempt"
required: false
default: "60"
retries:
description: "Number of retry attempts after initial run"
required: false
default: "3"
retry_delay_seconds:
description: "Delay between retries in seconds"
required: false
default: "30"
run:
description: "Command to run"
required: true
shell:
description: "Shell to use (bash or python)"
required: false
default: "bash"
runs:
using: "composite"
steps:
- name: Execute with retry (Python)
if: inputs.shell == 'python'
shell: python
env: ${{ env }}
run: |
import time
import sys
from pathlib import Path
import tempfile
start_time = time.time()
timeout_seconds = ${{ inputs.timeout_minutes }} * 60
attempt = 1
max_attempts = 1 + ${{ inputs.retries }} # Initial run + retries
# Create temporary script file
temp_path = Path(tempfile.gettempdir()) / 'retry_script.py'
temp_path.write_text("""${{ inputs.run }}""")
while attempt <= max_attempts:
if attempt > 1:
print(f"::group::Retry {attempt-1} of ${{ inputs.retries }}")
if (time.time() - start_time) > timeout_seconds:
print(f"::error::Step timed out after ${{ inputs.timeout_minutes }} minutes")
temp_path.unlink(missing_ok=True)
sys.exit(1)
try:
exec(temp_path.read_text())
if attempt > 1:
print("::endgroup::")
temp_path.unlink(missing_ok=True)
sys.exit(0)
except Exception as e:
if attempt > 1:
print(f"Retry {attempt-1} failed with error: {str(e)}")
print("::endgroup::")
else:
print(f"Initial attempt failed with error: {str(e)}")
if attempt == max_attempts:
print(f"::error::Step failed after initial attempt and {attempt-1} retries")
temp_path.unlink(missing_ok=True)
sys.exit(1)
print(f"Retrying in ${{ inputs.retry_delay_seconds }} seconds...")
time.sleep(${{ inputs.retry_delay_seconds }})
attempt += 1
- name: Execute with retry (Bash)
if: inputs.shell != 'python'
shell: bash
env: ${{ env }}
run: |
set +e
start_time=$(date +%s)
timeout_seconds=$(( ${{ inputs.timeout_minutes }} * 60 ))
attempt=1
max_attempts=$(( 1 + ${{ inputs.retries }} )) # Initial run + retries
# Create a temporary script file with proper shebang
TEMP_SCRIPT=$(mktemp)
cat << 'EOF' > "$TEMP_SCRIPT"
#!/bin/bash
set -e
${{ inputs.run }}
EOF
chmod +x "$TEMP_SCRIPT"
while [ $attempt -le $max_attempts ]; do
if [ $attempt -gt 1 ]; then
echo "::group::Retry $((attempt-1)) of ${{ inputs.retries }}"
fi
current_time=$(date +%s)
if [ $((current_time - start_time)) -gt $timeout_seconds ]; then
echo "::error::Step timed out after ${{ inputs.timeout_minutes }} minutes"
rm "$TEMP_SCRIPT"
exit 1
fi
# Execute the temporary script
"$TEMP_SCRIPT"
exit_code=$?
if [ $exit_code -eq 0 ]; then
[ $attempt -gt 1 ] && echo "::endgroup::"
rm "$TEMP_SCRIPT"
exit 0
fi
if [ $attempt -gt 1 ]; then
echo "Retry $((attempt-1)) failed with exit code $exit_code"
echo "::endgroup::"
else
echo "Initial attempt failed with exit code $exit_code"
fi
if [ $attempt -eq $max_attempts ]; then
echo "::error::Step failed after initial attempt and $((attempt-1)) retries"
rm "$TEMP_SCRIPT"
exit $exit_code
fi
echo "Retrying in ${{ inputs.retry_delay_seconds }} seconds..."
sleep ${{ inputs.retry_delay_seconds }}
attempt=$((attempt + 1))
done