Dispatch MSBuild Investigation (2 parallel) #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Throttled dispatcher for msbuild-investigate-issue workflows. | |
| # | |
| # Dispatches upstream issues in batches with a configurable parallelism limit, | |
| # waiting for each batch to complete before starting the next. | |
| # After all investigation batches finish, dispatches the summary workflow | |
| # to consolidate results into the discussion. | |
| # | |
| # Designed to be dispatched from the MSBuild Weekly Report agent with a | |
| # JSON array of upstream issue numbers and the discussion ID. | |
| # | |
| # Timing budget (worst-case): | |
| # A single investigation can take up to ~45 min (agent: 20m + safe_outputs: 15m | |
| # + activation/conclusion: ~10m). | |
| # With max_parallel=2 and 10 issues → 5 batches × ~60 min = ~300 min. | |
| # Job timeout is set to 360 min (6 hours) to accommodate this. | |
| name: "Dispatch MSBuild Investigation Batch" | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| issues_json: | |
| description: 'JSON array of upstream issue numbers (e.g. ["1234","5678"])' | |
| required: true | |
| type: string | |
| upstream_repo: | |
| description: 'Upstream repo (owner/name)' | |
| required: false | |
| default: 'dotnet/msbuild' | |
| type: string | |
| max_parallel: | |
| description: 'Max number of investigation workflows running concurrently' | |
| required: false | |
| default: '2' | |
| type: string | |
| permissions: | |
| actions: write | |
| run-name: "Dispatch MSBuild Investigation (${{ inputs.max_parallel }} parallel)" | |
| jobs: | |
| dispatch: | |
| runs-on: ubuntu-latest | |
| # 6 hours — enough for batches at ~45 min per investigation | |
| timeout-minutes: 360 | |
| steps: | |
| - name: Validate inputs | |
| env: | |
| ISSUES_JSON: ${{ inputs.issues_json }} | |
| MAX_PARALLEL: ${{ inputs.max_parallel }} | |
| run: | | |
| if ! echo "$ISSUES_JSON" | jq -e 'type == "array" and length > 0' > /dev/null 2>&1; then | |
| echo "::error::issues_json must be a non-empty JSON array" | |
| exit 1 | |
| fi | |
| if ! [[ "$MAX_PARALLEL" =~ ^[1-9][0-9]*$ ]]; then | |
| echo "::error::max_parallel must be a positive integer" | |
| exit 1 | |
| fi | |
| COUNT=$(echo "$ISSUES_JSON" | jq 'length') | |
| echo "### Dispatch MSBuild Investigation Batch" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- **Issues to investigate:** ${COUNT}" >> "$GITHUB_STEP_SUMMARY" | |
| echo "- **Max parallel:** ${MAX_PARALLEL}" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| - name: Find latest weekly report discussion | |
| id: find-discussion | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| # Parse owner and repo name | |
| OWNER="${REPO%%/*}" | |
| REPO_NAME="${REPO##*/}" | |
| # Search for the most recent MSBuild Weekly Report discussion | |
| DISCUSSION_ID=$(gh api graphql -f query=' | |
| query($owner: String!, $repo: String!) { | |
| repository(owner: $owner, name: $repo) { | |
| discussions(first: 5, orderBy: {field: CREATED_AT, direction: DESC}) { | |
| nodes { | |
| number | |
| title | |
| } | |
| } | |
| } | |
| } | |
| ' -f owner="$OWNER" -f repo="$REPO_NAME" \ | |
| --jq '.data.repository.discussions.nodes[] | select(.title | startswith("MSBuild Weekly Report")) | .number' \ | |
| | head -1) | |
| if [ -z "$DISCUSSION_ID" ]; then | |
| echo "::warning::No MSBuild Weekly Report discussion found. Investigation comments will not be posted." | |
| DISCUSSION_ID="0" | |
| fi | |
| echo "discussion_id=${DISCUSSION_ID}" >> "$GITHUB_OUTPUT" | |
| echo "Found discussion #${DISCUSSION_ID}" | |
| echo "- **Discussion:** #${DISCUSSION_ID}" >> "$GITHUB_STEP_SUMMARY" | |
| - name: Dispatch investigation workflows in throttled batches | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| ISSUES_JSON: ${{ inputs.issues_json }} | |
| MAX_PARALLEL: ${{ inputs.max_parallel }} | |
| UPSTREAM_REPO: ${{ inputs.upstream_repo }} | |
| DISCUSSION_ID: ${{ steps.find-discussion.outputs.discussion_id }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| readarray -t ISSUES < <(echo "$ISSUES_JSON" | jq -r '.[]') | |
| TOTAL=${#ISSUES[@]} | |
| echo "Total issues to investigate: ${TOTAL}" | |
| echo "Max parallel: ${MAX_PARALLEL}" | |
| echo "Discussion ID: ${DISCUSSION_ID}" | |
| echo "" | |
| batch_num=0 | |
| dispatched=0 | |
| all_results="" | |
| for ((i=0; i<TOTAL; i+=MAX_PARALLEL)); do | |
| batch_num=$((batch_num + 1)) | |
| BATCH=("${ISSUES[@]:i:MAX_PARALLEL}") | |
| BATCH_SIZE=${#BATCH[@]} | |
| echo "==========================================" | |
| echo "Batch ${batch_num}: ${BATCH_SIZE} issue(s) — ${BATCH[*]}" | |
| echo "==========================================" | |
| # Dispatch all issues in this batch | |
| for ISSUE in "${BATCH[@]}"; do | |
| echo " → Dispatching investigation for upstream issue #${ISSUE}" | |
| gh workflow run msbuild-investigate-issue.lock.yml \ | |
| --repo "${REPO}" \ | |
| -f upstream_issue="${ISSUE}" \ | |
| -f upstream_repo="${UPSTREAM_REPO}" \ | |
| -f discussion_id="${DISCUSSION_ID}" | |
| dispatched=$((dispatched + 1)) | |
| all_results="${all_results}| ${ISSUE} | dispatched |"$'\n' | |
| # Rate-limit dispatches to avoid GitHub API throttling | |
| sleep 5 | |
| done | |
| # Check if there are more batches after this one | |
| REMAINING=$((TOTAL - i - BATCH_SIZE)) | |
| if [ "$REMAINING" -le 0 ]; then | |
| echo "" | |
| echo "Last batch dispatched. Waiting for completion..." | |
| else | |
| echo "" | |
| echo "Waiting for batch ${batch_num} to complete before dispatching next ${REMAINING} issue(s)..." | |
| fi | |
| # Wait for dispatched runs to register in the API | |
| sleep 30 | |
| # Poll until all active investigation runs finish | |
| # A single investigation can take up to ~45 min, so we allow 75 min per batch | |
| BATCH_TIMEOUT=4500 # 75 minutes | |
| ELAPSED=0 | |
| POLL_INTERVAL=30 | |
| while [ $ELAPSED -lt $BATCH_TIMEOUT ]; do | |
| # Count in-progress, queued, and waiting investigation runs | |
| ACTIVE_IN_PROGRESS=$(gh api \ | |
| "repos/${REPO}/actions/workflows/msbuild-investigate-issue.lock.yml/runs?status=in_progress" \ | |
| --jq '.total_count' 2>/dev/null || echo "0") | |
| ACTIVE_QUEUED=$(gh api \ | |
| "repos/${REPO}/actions/workflows/msbuild-investigate-issue.lock.yml/runs?status=queued" \ | |
| --jq '.total_count' 2>/dev/null || echo "0") | |
| ACTIVE_WAITING=$(gh api \ | |
| "repos/${REPO}/actions/workflows/msbuild-investigate-issue.lock.yml/runs?status=waiting" \ | |
| --jq '.total_count' 2>/dev/null || echo "0") | |
| ACTIVE=$((ACTIVE_IN_PROGRESS + ACTIVE_QUEUED + ACTIVE_WAITING)) | |
| if [ "$ACTIVE" -le 0 ]; then | |
| echo " ✅ Batch ${batch_num} complete — all investigation runs finished." | |
| break | |
| fi | |
| MINUTES=$((ELAPSED / 60)) | |
| echo " ⏳ ${ACTIVE} investigation run(s) still active (${MINUTES}m elapsed, timeout at $((BATCH_TIMEOUT / 60))m)..." | |
| sleep $POLL_INTERVAL | |
| ELAPSED=$((ELAPSED + POLL_INTERVAL)) | |
| done | |
| if [ $ELAPSED -ge $BATCH_TIMEOUT ]; then | |
| echo " ⚠️ Batch ${batch_num} timed out after $((BATCH_TIMEOUT / 60))m — some runs may still be active." | |
| echo " Proceeding to next batch anyway." | |
| fi | |
| echo "" | |
| done | |
| echo "==========================================" | |
| echo "Summary: Dispatched ${dispatched}/${TOTAL} investigation workflows" | |
| echo " across ${batch_num} batch(es)" | |
| echo "==========================================" | |
| # Write summary table | |
| { | |
| echo "| Issue | Status |" | |
| echo "|-------|--------|" | |
| echo "$all_results" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Dispatch summary workflow | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| DISCUSSION_ID: ${{ steps.find-discussion.outputs.discussion_id }} | |
| run: | | |
| echo "Dispatching investigation summary workflow..." | |
| gh workflow run msbuild-investigation-summary.lock.yml \ | |
| --repo "${REPO}" \ | |
| -f discussion_id="${DISCUSSION_ID}" | |
| echo " ✅ Summary workflow dispatched for discussion #${DISCUSSION_ID}" |