diff --git a/.github/workflows/release-stage-1_update_dependencies.yml b/.github/workflows/release-stage-1_update_dependencies.yml index 6db47c7..89e7f95 100644 --- a/.github/workflows/release-stage-1_update_dependencies.yml +++ b/.github/workflows/release-stage-1_update_dependencies.yml @@ -45,6 +45,7 @@ jobs: name: Update ${{ matrix.release_type }} Dependencies needs: determine-release-types strategy: + max-parallel: 1 matrix: ${{ fromJson(needs.determine-release-types.outputs.matrix) }} runs-on: ubuntu-latest permissions: diff --git a/RACE_CONDITION_FIX.md b/RACE_CONDITION_FIX.md new file mode 100644 index 0000000..2749b29 --- /dev/null +++ b/RACE_CONDITION_FIX.md @@ -0,0 +1,60 @@ +# Race Condition Fix for Release Stage 1 Workflow + +This document explains the fix implemented for the race condition in the Release Stage 1 workflow that was causing "GraphQL: Base branch was modified. Review and try the merge again" errors. + +## Problem + +The Release Stage 1 workflow was failing with race condition errors when multiple dependency update bots (alpha, beta, stable) tried to merge PRs simultaneously: + +1. All three release types would run in parallel using the matrix strategy +2. Each would create PRs and attempt to auto-merge them at the same time +3. When one job merged its PR (modifying the base branch), other jobs would fail with "Base branch was modified" +4. This resulted in workflow failures like run #17525831245 + +## Solution + +The fix was implemented by adding a simple `max-parallel: 1` constraint to the existing matrix strategy: + +```yaml +strategy: + max-parallel: 1 + matrix: ${{ fromJson(needs.determine-release-types.outputs.matrix) }} +``` + +## Benefits + +This approach provides several advantages: + +- **✅ Simple and minimal** - Only one line added to the existing workflow +- **✅ Preserves matrix structure** - Maintains the clean matrix approach instead of duplicating jobs +- **✅ Prevents race conditions** - Ensures only one bot operates at a time +- **✅ No code duplication** - Keeps the DRY principle intact +- **✅ Easy to understand** - Clear intent and implementation +- **✅ Maintains existing logic** - All conditional execution and matrix variables preserved + +## How It Works + +The `max-parallel: 1` constraint ensures that: + +1. The matrix still defines all three jobs (alpha, beta, stable) +2. GitHub Actions will only run one matrix job at a time +3. Jobs execute sequentially, preventing simultaneous PR merge attempts +4. Race conditions are eliminated while preserving the clean matrix structure + +## Testing + +The fix includes a comprehensive test suite that validates: + +- YAML syntax correctness +- Matrix strategy with max-parallel constraint +- Single job structure (not separate sequential jobs) +- Matrix variable usage preservation +- Proper race condition prevention + +Run tests with: + +```bash +./test/test-race-condition-fix.sh +``` + +This fix resolves the race condition without the complexity of separate sequential jobs, making it both effective and maintainable. \ No newline at end of file diff --git a/test/test-race-condition-fix.sh b/test/test-race-condition-fix.sh new file mode 100755 index 0000000..8196a46 --- /dev/null +++ b/test/test-race-condition-fix.sh @@ -0,0 +1,152 @@ +#!/bin/bash + +# Test script to validate the race condition fix for Release Stage 1 +# This script validates that the workflow uses max-parallel: 1 to prevent race conditions + +set -e + +echo "🧪 Testing Race Condition Fix for Release Stage 1..." +echo + +# Test 1: Validate YAML syntax +echo "Test 1: Validating YAML syntax..." +if python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release-stage-1_update_dependencies.yml'))" 2>/dev/null; then + echo "✅ YAML syntax is valid" +else + echo "❌ YAML syntax error" + exit 1 +fi + +# Test 2: Check that matrix strategy still exists but with max-parallel constraint +echo +echo "Test 2: Checking matrix strategy with max-parallel constraint..." +WORKFLOW_FILE=".github/workflows/release-stage-1_update_dependencies.yml" + +# Check that matrix strategy is present +if grep -q "strategy:" "$WORKFLOW_FILE"; then + echo "✅ Matrix strategy found" +else + echo "❌ Matrix strategy missing" + exit 1 +fi + +# Check that matrix configuration is present +if grep -q "matrix:" "$WORKFLOW_FILE"; then + echo "✅ Matrix configuration found" +else + echo "❌ Matrix configuration missing" + exit 1 +fi + +# Test 3: Check that max-parallel: 1 constraint is present +echo +echo "Test 3: Checking max-parallel constraint..." + +if grep -q "max-parallel: 1" "$WORKFLOW_FILE"; then + echo "✅ max-parallel: 1 constraint found" +else + echo "❌ max-parallel: 1 constraint missing" + exit 1 +fi + +# Test 4: Check that single update_dependencies job exists (not separate jobs) +echo +echo "Test 4: Checking single matrix job structure..." + +if grep -q "update_dependencies:" "$WORKFLOW_FILE"; then + echo "✅ Single update_dependencies job found" +else + echo "❌ update_dependencies job missing" + exit 1 +fi + +# Ensure separate alpha/beta/stable jobs don't exist (those were from the old approach) +if grep -q "update_alpha_dependencies:" "$WORKFLOW_FILE"; then + echo "❌ Separate alpha job found (should use matrix instead)" + exit 1 +else + echo "✅ No separate alpha job (using matrix approach correctly)" +fi + +# Ensure separate beta/stable jobs don't exist (those were from the old approach) +if grep -q "update_beta_dependencies:" "$WORKFLOW_FILE"; then + echo "❌ Separate beta job found (should use matrix instead)" + exit 1 +else + echo "✅ No separate beta job (using matrix approach correctly)" +fi + +if grep -q "update_stable_dependencies:" "$WORKFLOW_FILE"; then + echo "❌ Separate stable job found (should use matrix instead)" + exit 1 +else + echo "✅ No separate stable job (using matrix approach correctly)" +fi + +# Test 5: Check that the job still uses matrix variables properly +echo +echo "Test 5: Checking matrix variable usage..." + +if grep -q "\${{ matrix.release_type }}" "$WORKFLOW_FILE"; then + echo "✅ Matrix release_type variable found" +else + echo "❌ Matrix release_type variable missing" + exit 1 +fi + +if grep -q "\${{ matrix.config_file }}" "$WORKFLOW_FILE"; then + echo "✅ Matrix config_file variable found" +else + echo "❌ Matrix config_file variable missing" + exit 1 +fi + +# Test 6: Check that the job name uses matrix variable +echo +echo "Test 6: Checking matrix job name..." + +if grep -q "Update \${{ matrix.release_type }} Dependencies" "$WORKFLOW_FILE"; then + echo "✅ Job name uses matrix variable correctly" +else + echo "❌ Job name does not use matrix variable" + exit 1 +fi + +# Test 7: Verify that race condition is prevented +echo +echo "Test 7: Verifying race condition prevention..." + +# Check that max-parallel and matrix are in the same strategy block +STRATEGY_SECTION=$(python3 -c " +import yaml +with open('$WORKFLOW_FILE') as f: + w = yaml.safe_load(f) + strategy = w['jobs']['update_dependencies']['strategy'] + print('max-parallel' in strategy and 'matrix' in strategy) +" 2>/dev/null) + +if [[ "$STRATEGY_SECTION" == "True" ]]; then + echo "✅ max-parallel and matrix are properly configured in strategy" +else + echo "❌ max-parallel and matrix not properly configured" + exit 1 +fi + +echo +echo "🎉 All tests passed! The race condition fix is properly implemented." +echo +echo "Summary of changes:" +echo " - ✅ Kept the clean matrix strategy structure" +echo " - ✅ Added max-parallel: 1 constraint to prevent race conditions" +echo " - ✅ Maintained all existing matrix variable usage" +echo " - ✅ Preserved conditional execution logic" +echo " - ✅ Much simpler solution than sequential jobs" +echo +echo "This should resolve the race condition that caused:" +echo " - 'Base branch was modified. Review and try the merge again' errors" +echo " - Failed PR merges when multiple bots run simultaneously" +echo " - Release Stage 1 workflow failures" +echo +echo "The max-parallel: 1 constraint ensures that even though the matrix" +echo "defines multiple jobs (alpha, beta, stable), only one will run at a time," +echo "preventing the race condition while maintaining the clean matrix structure." \ No newline at end of file