159 lines
5.1 KiB
YAML
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
|