From 57ecfd4270e6abb450df221b225861e3fb40b9af Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Sat, 12 Sep 2026 09:15:57 -0600 Subject: [PATCH] ci: retry the Maven distribution download in every job that runs mvnw The retry and cache added in #5422 only covered callers of the java-test action. Every other Maven caller (lint-java, build-spark-4-1, the benchmark verify jobs, setup-spark-builder, the Iceberg installs, preflight's RAT check) still downloads apache-maven-3.9.6-bin.zip on a cache miss with no retry, so one 403 from Maven Central fails the job before it builds anything. Move the restore/retry/save block into a bootstrap-maven composite action and call it at the end of setup-builder and setup-macos-builder, which every ./mvnw caller runs first, plus directly from preflight. Route edits to the new action to the same jobs as the setup actions. --- .github/actions/bootstrap-maven/action.yaml | 78 +++++++++++++++++++ .github/actions/java-test/action.yaml | 45 +---------- .github/actions/setup-builder/action.yaml | 5 ++ .../actions/setup-macos-builder/action.yaml | 5 ++ .github/workflows/ci.yml | 5 ++ dev/ci/check-ci-config.py | 3 + dev/ci/compute-changes.py | 10 +++ 7 files changed, 110 insertions(+), 41 deletions(-) create mode 100644 .github/actions/bootstrap-maven/action.yaml diff --git a/.github/actions/bootstrap-maven/action.yaml b/.github/actions/bootstrap-maven/action.yaml new file mode 100644 index 00000000000..950de8a7d2c --- /dev/null +++ b/.github/actions/bootstrap-maven/action.yaml @@ -0,0 +1,78 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: Bootstrap Maven +description: >- + Install the Maven distribution the wrapper points at, retrying the download + and caching the result, so a Maven Central hiccup does not fail a job before + it has built or tested anything. + +# The dependency caches cover ~/.m2/repository, but the wrapper installs Maven +# itself under ~/.m2/wrapper/dists. On a fresh runner the first ./mvnw call +# therefore still downloads apache-maven-*-bin.zip from Maven Central, and that +# single request has failed with HTTP 403 and 429 often enough to matter. +# Every job that runs ./mvnw (directly, via make, or via setup-spark-builder) +# goes through setup-builder or setup-macos-builder, which end by calling this +# action; ci.yml's preflight job calls it directly before the RAT check. +# +# Requires a checkout and a JDK on PATH. Runs from the repository root. + +runs: + using: "composite" + steps: + # Maven itself is stored outside the dependency repository. Keep its cache + # independent of pom.xml changes. + - name: Restore Maven distribution + id: maven-distribution + # Temporarily disabled on macOS to work around + # https://github.com/actions/runner-images/issues/13341; macOS still + # gets the retry below, just not the cache. + if: ${{ runner.os != 'macOS' }} + uses: actions/cache/restore@v5 + with: + path: | + ~/.m2/wrapper/dists + /root/.m2/wrapper/dists + key: ${{ runner.os }}-${{ runner.arch }}-maven-wrapper-${{ hashFiles('.mvn/wrapper/maven-wrapper.properties') }} + + # Retry only the wrapper download, never compilation or test execution. + # Delays use exponential backoff (10s, 20s, 40s) plus 0-4s of random jitter. + - name: Bootstrap Maven + shell: bash + run: | + for attempt in 1 2 3 4; do + if ./mvnw -B --version; then + break + fi + if [ "$attempt" -eq 4 ]; then + echo "::error::Maven bootstrap failed after $attempt attempts; nothing was built." + exit 1 + fi + delay=$((10 * (1 << (attempt - 1)) + RANDOM % 5)) + echo "::warning::Maven bootstrap attempt $attempt failed; retrying in ${delay}s." + sleep "$delay" + done + + # Save a successful bootstrap even when the rest of the job fails. + - name: Save Maven distribution + if: ${{ runner.os != 'macOS' && steps.maven-distribution.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v5 + with: + path: | + ~/.m2/wrapper/dists + /root/.m2/wrapper/dists + key: ${{ steps.maven-distribution.outputs.cache-primary-key }} diff --git a/.github/actions/java-test/action.yaml b/.github/actions/java-test/action.yaml index 42078b3c44d..d81c0cd92eb 100644 --- a/.github/actions/java-test/action.yaml +++ b/.github/actions/java-test/action.yaml @@ -76,45 +76,9 @@ runs: restore-keys: | ${{ runner.os }}-java-maven- - # Maven itself is stored outside the dependency repository. Keep its cache - # independent of pom.xml changes and preserve the macOS cache workaround. - - name: Restore Maven distribution - id: maven-distribution - if: ${{ runner.os != 'macOS' }} - uses: actions/cache/restore@v5 - with: - path: | - ~/.m2/wrapper/dists - /root/.m2/wrapper/dists - key: ${{ runner.os }}-${{ runner.arch }}-maven-wrapper-${{ hashFiles('.mvn/wrapper/maven-wrapper.properties') }} - - # Retry only the wrapper download, never compilation or test execution. - # Delays use exponential backoff (10s, 20s, 40s) plus 0-4s of random jitter. - - name: Bootstrap Maven - shell: bash - run: | - for attempt in 1 2 3 4; do - if ./mvnw -B --version; then - break - fi - if [ "$attempt" -eq 4 ]; then - echo "::error::Maven bootstrap failed after $attempt attempts; tests were not started." - exit 1 - fi - delay=$((10 * (1 << (attempt - 1)) + RANDOM % 5)) - echo "::warning::Maven bootstrap attempt $attempt failed; retrying in ${delay}s." - sleep "$delay" - done - - # Save a successful bootstrap even when the subsequent tests fail. - - name: Save Maven distribution - if: ${{ runner.os != 'macOS' && steps.maven-distribution.outputs.cache-hit != 'true' }} - uses: actions/cache/save@v5 - with: - path: | - ~/.m2/wrapper/dists - /root/.m2/wrapper/dists - key: ${{ steps.maven-distribution.outputs.cache-primary-key }} + # Maven itself is already installed (with download retries and its own + # cache) by setup-builder / setup-macos-builder via bootstrap-maven, which + # every caller of this action runs first. - name: Run all tests shell: bash @@ -137,8 +101,7 @@ runs: - name: Upload crash logs if: failure() # These three stay on the plain action rather than - # ../upload-artifact-retry: a local action calling another local action - # is untested in this repo. The two failure-only uploads run on jobs that + # ../upload-artifact-retry. The two failure-only uploads run on jobs that # are already red. The test-report upload also runs on green jobs, so it # is continue-on-error: a FinalizeArtifact 403 must not turn a passing test # run into a red check, and nothing downstream consumes the reports. diff --git a/.github/actions/setup-builder/action.yaml b/.github/actions/setup-builder/action.yaml index 0ccd01ad726..a0af8ed96b0 100644 --- a/.github/actions/setup-builder/action.yaml +++ b/.github/actions/setup-builder/action.yaml @@ -56,3 +56,8 @@ runs: rustup toolchain install ${{inputs.rust-version}} rustup default ${{inputs.rust-version}} rustup component add rustfmt clippy + + # Runs last so the JDK is on PATH. Retries the Maven distribution download + # and caches it; see .github/actions/bootstrap-maven/action.yaml. + - name: Bootstrap Maven + uses: ./.github/actions/bootstrap-maven diff --git a/.github/actions/setup-macos-builder/action.yaml b/.github/actions/setup-macos-builder/action.yaml index 7c1c8b522ed..b31cd9a596c 100644 --- a/.github/actions/setup-macos-builder/action.yaml +++ b/.github/actions/setup-macos-builder/action.yaml @@ -78,3 +78,8 @@ runs: rustup toolchain install ${{inputs.rust-version}} rustup default ${{inputs.rust-version}} rustup component add rustfmt clippy + + # Runs last so the JDK is on PATH. Retries the Maven distribution download + # and caches it; see .github/actions/bootstrap-maven/action.yaml. + - name: Bootstrap Maven + uses: ./.github/actions/bootstrap-maven diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9754b89d4d0..7928830b552 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,11 @@ jobs: distribution: temurin java-version: 11 + # Preflight gates every other job, so a single failed download of the + # Maven distribution here would fail the whole run. + - name: Bootstrap Maven + uses: ./.github/actions/bootstrap-maven + - name: Apache RAT license check run: ./mvnw -B -N apache-rat:check diff --git a/dev/ci/check-ci-config.py b/dev/ci/check-ci-config.py index 0d30910f298..1fdc3279470 100644 --- a/dev/ci/check-ci-config.py +++ b/dev/ci/check-ci-config.py @@ -103,6 +103,9 @@ # to nothing at all and merges having been exercised by no consumer. ([".github/actions/upload-artifact-retry/action.yaml"], BUILD_JOBS), ([".github/actions/download-artifact-retry/action.yaml"], BUILD_JOBS), + # Maven bootstrap runs inside setup-builder and setup-macos-builder, so it + # reaches every job that runs ./mvnw. + ([".github/actions/bootstrap-maven/action.yaml"], BUILD_JOBS), # Spot checks that the additions above did not widen unrelated routes. (["docs/source/user-guide/overview.md"], {"docs"}), (["native/core/benches/parquet_read.rs"], {"benchmark"}), diff --git a/dev/ci/compute-changes.py b/dev/ci/compute-changes.py index 9716ced2342..e694607d418 100644 --- a/dev/ci/compute-changes.py +++ b/dev/ci/compute-changes.py @@ -58,6 +58,7 @@ ".github/actions/rust-test/**", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", "!**.md", "!native/core/benches/**", "!native/spark-expr/benches/**", @@ -82,6 +83,7 @@ ".github/actions/java-test/**", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", "!**.md", "!native/core/benches/**", "!native/spark-expr/benches/**", @@ -129,6 +131,7 @@ ".github/actions/setup-spark-builder/**", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", ".mvn/**", "mvnw", ], @@ -155,6 +158,7 @@ ".github/actions/setup-spark-builder/**", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", ".mvn/**", "mvnw", ], @@ -181,6 +185,7 @@ ".github/actions/setup-spark-builder/**", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", ".mvn/**", "mvnw", ], @@ -207,6 +212,7 @@ ".github/actions/setup-spark-builder/**", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", ".mvn/**", "mvnw", ], @@ -231,6 +237,7 @@ "dev/ci/test-iceberg-shards.py", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", ".mvn/**", "mvnw", ], @@ -255,6 +262,7 @@ "dev/ci/test-iceberg-shards.py", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", ".mvn/**", "mvnw", ], @@ -279,6 +287,7 @@ "dev/ci/test-iceberg-shards.py", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", ".mvn/**", "mvnw", ], @@ -303,6 +312,7 @@ "dev/ci/test-iceberg-shards.py", ".github/actions/upload-artifact-retry/**", ".github/actions/download-artifact-retry/**", + ".github/actions/bootstrap-maven/**", ".mvn/**", "mvnw", ],