Repository navigation
Conversation
sunchao
left a comment
There was a problem hiding this comment.
Correctness
The current lint step runs one Maven package plus semantic Scalafix check. This PR wraps that same command in four attempts, retains the latest output through tee, and stops immediately when the output contains Scalafix invoked with errors. The Maven profiles, check mode and SemanticDB option stay the same.
There is a blocking regression in the successful path: scalafix-check.log remains in the checkout, and the existing check-working-tree-clean.sh rejects untracked files. All four Java lint rows at merge 3f73798031d96bfa758bcb360750b1e5a3b9a2e3 print BUILD SUCCESS, then fail with ?? scalafix-check.log. Please write the log under $RUNNER_TEMP and read that same path, or remove it before the clean-tree check. The inline comment includes a current-run example.
This is a CI-only change. It does not alter Spark expressions, operators, types, null handling, ANSI errors or fallback behavior. The existing Spark 3.4, 3.5 and two Spark 4.0 lint rows remain selected. No Spark runtime equivalence or JVM execution was established by the local probes.
Validation
I ran 18 controlled shell cases using the exact extracted step across the three distinct Maven option strings. Success returns zero, a resolution failure followed by success retries, a recognized lint violation stops immediately, and persistent failures remain nonzero after four attempts. Failure codes are normalized to 1. These use Maven and sleep doubles in local Bash, not a live network or Maven build.
Current Comet CI and Required Checks are failed because of the four dirty-tree lint failures. The syntactic lint and the selected Spark 4.1 tests pass. The author reports actionlint success, which I have not rerun. The CI logs show that the actual container shell executes the Scalafix step successfully, so I am not raising a shell-startup issue.
Performance
A successful first attempt adds log capture without rerunning Maven. On failure, however, the retry condition accepts every error that lacks one Scalafix message. A deterministic Scala compilation failure also takes four attempts in the controlled test, repeating the package command three extra times and adding 70–82 seconds of scheduled backoff. The second inline finding asks to restrict this to positively identified transient resolution failures. The attempt counts are verified, but the probes do not measure JVM build time.
Design
Keeping a bounded retry beside the existing invocation is straightforward, and preserving a nonzero result after exhaustion keeps the gate closed. The two failure paths need clearer separation: expected transient resolution failures should retry, while compilation, configuration and lint failures should exit immediately. Moving the diagnostic log outside the checkout also preserves the downstream clean-tree invariant without weakening that check.
Abstraction & complexity
The loop is small and does not introduce a new reusable action or application abstraction. A temporary log path and a narrow retry classifier fit this scope. The contributor guide's formatting and build instructions remain accurate because the underlying commands and runtime behavior are unchanged.
| set -o pipefail | ||
| for attempt in 1 2 3 4; do | ||
| if ./mvnw -B package -DskipTests scalafix:scalafix -Dscalafix.mode=CHECK \ | ||
| -Psemanticdb ${{ matrix.profile.maven_opts }} 2>&1 | tee scalafix-check.log; then |
There was a problem hiding this comment.
Correctness
[P1] Keep the captured log outside the checkout
Could this log be written under $RUNNER_TEMP and read from that same path? tee scalafix-check.log creates an untracked file on every successful run, and the later ./dev/ci/check-working-tree-clean.sh fails on any untracked file. All four current Java lint rows already show BUILD SUCCESS followed by Working tree is not clean: ?? scalafix-check.log (Spark 4.0/JDK 21 log). This makes Required Checks fail even when Scalafix passes.
There was a problem hiding this comment.
Fixed in 49ff6ce. The log now goes to a mktemp file outside the checkout, the same way the sbt retry in spark_sql_test_reusable.yml handles it, so a passing run leaves the tree clean.
| -Psemanticdb ${{ matrix.profile.maven_opts }} 2>&1 | tee scalafix-check.log; then | ||
| exit 0 | ||
| fi | ||
| if grep -q 'Scalafix invoked with errors' scalafix-check.log; then |
There was a problem hiding this comment.
Performance
[P2] Retry only recognized transient resolution failures
Could the retry branch require a known transient Maven resolution error, and stop for other failures? This invocation includes package, so a deterministic Scala compilation error exits nonzero without Scalafix invoked with errors. Running the extracted step with that failure repeats the same Maven command four times and adds 70–82 seconds of backoff before returning failure. This repeats compilation for an unchanged broken input in every affected matrix row. The existing bootstrap action avoids this by retrying only its download operation. A focused case for a compiler failure alongside the resolution and lint cases would guard the distinction.
There was a problem hiding this comment.
Agreed, fixed in 49ff6ce. The loop now retries only when one of Maven's [ERROR] lines names a download (No plugin found for prefix, could not be resolved, Could not transfer, or the scala.tools.nsc.Main ClassNotFoundException that a missing compiler jar produces), and anything else fails on the first attempt. Replaying it over 111 Lint Java step logs from CI runs since 09-20, it retries the six download failures and none of the 30 compile errors or 25 lint violations.
sunchao
left a comment
There was a problem hiding this comment.
Summary
- Prior state and problem: A transient Maven resolution failure immediately failed the semantic Scalafix job.
- Design approach: Wrap the existing Maven command in four attempts, capture output, and stop when the recognized Scalafix error marker appears.
- Correctness / compatibility analysis: The existing P1 log-file concern remains confirmed. Successful execution leaves
scalafix-check.loguntracked, failing the downstream clean-tree check. Spark runtime semantics and the existing Spark 3.4, 3.5 and 4.0 lint profiles are unchanged. - Key design decisions: The bounded inline loop adds little abstraction or complexity. Keeping the original command preserves its build and lint behavior, but also makes the retry encompass compilation.
- Implementation sketch: Execute through
teewithpipefail, return on success, fail on the marker or exhausted attempts, otherwise sleep and retry. - Behavioral changes worth calling out: The existing P2 retry-classification concern remains confirmed. A deterministic compiler failure invokes Maven four times and schedules at least 70 seconds of backoff. The successful path invokes Maven once.
- Suggested improvements: Address the existing threads by placing the log under
$RUNNER_TEMPand retrying only recognized transient resolution failures. No additional introduced P1/P2 issues found within this review.
Reviewed the complete diff from d1bf687ebb13c1439057d0e7e2d15286f233c3d0 to 72244ad5ceb294364df6e2662eb4a402c508a8a8. The PR is not a draft. Read AGENTS.md, contributor guidance, and all existing reviews, comments and threads. Routed skill: review-comet-pr. No sibling skill applies to this CI-only change.
Exact-head CI: run 35516961938 has 19 successful, five failed and 14 skipped checks. All four Java lint rows show BUILD SUCCESS, then fail on ?? scalafix-check.log, making Required Checks fail. Verified that the tested merge commit has the requested base and head as parents. Preflight, including actionlint, passed.
Validation: check-ci-config.py passed. Twenty-eight disposable Bash cases across all four matrix profiles confirmed success, retry, lint failure, exhaustion and compiler-failure behavior, and reproduced the dirty-tree failure. Maven and sleep were stubbed. No local Maven/JVM build, Spark execution or actionlint run was performed. No Spark semantic source comparison was applicable to this workflow-only diff. Project files remain unchanged.
|
Hi @andygrove |
sunchao
left a comment
There was a problem hiding this comment.
Summary
- Prior state and problem: The semantic Scalafix step ran Maven once, so transient plugin-resolution failures immediately failed the lint gate, as described in #6060.
- Design approach: Wrap the existing Maven invocation in four attempts, capture its output with
tee, and stop immediately on the recognized Scalafix error marker. - Correctness: The existing P1 log-file concern remains confirmed. Successful execution leaves
scalafix-check.loguntracked, causing the downstream clean-tree check to fail. All four Java lint CI logs showBUILD SUCCESSfollowed by this failure. Controlled tests confirmed that Maven failures are not hidden byteeand that recognized lint errors stop retries. - Compatibility analysis: The Maven arguments, semantic lint mode and Spark 3.4, 3.5 and 4.0 lint profiles are unchanged. The existing Spark 4.1/4.2 semantic-lint exclusions also remain unchanged. No Spark expression, operator, shim or runtime behavior changes, so comparison against Spark semantic sources is not applicable.
- Key design decisions: Retrying the complete
packageplus Scalafix invocation preserves rule resolution online. The four-attempt limit bounds retries, but the marker identifies only one failure category and does not distinguish transient resolution failures from deterministic build errors. - Implementation sketch: Enable
pipefail, execute Maven throughtee, return on success, fail on the lint marker or fourth unsuccessful attempt, otherwise sleep and retry. - Performance: The existing P2 retry-classification concern remains substantiated. Injecting a deterministic compiler error invokes Maven four times and schedules 70–82 seconds of backoff in Bash. A successful first attempt invokes Maven once and adds log capture. No Maven build-time benchmark was performed.
- Design: A bounded inline retry loop is straightforward for this step. Its integration currently violates the downstream clean-tree requirement, and its broad retry condition repeats work for failures that cannot recover. Both issues are already covered by existing threads.
- Abstraction & complexity: No additional action or application abstraction is introduced. A temporary log location and a narrow retry classifier fit the current structure without requiring a broader refactor.
- Behavioral changes worth calling out: Non-marker failures now retry, terminal failures return status 1, and diagnostics distinguish recognized lint errors from exhausted retries. The generated log currently makes otherwise successful Java lint jobs fail. Product behavior and configuration remain unchanged.
- Suggested improvements: Address the existing threads by writing and reading the log under
$RUNNER_TEMPand retrying only recognized transient resolution failures. No additional introduced P1/P2 issues found within this review.
Reviewed the entire base-relative diff from d1bf687ebb13c1439057d0e7e2d15286f233c3d0 to 72244ad5ceb294364df6e2662eb4a402c508a8a8: one commit and one workflow file. Confirmed the PR is not a draft. Read AGENTS.md, contributor guidance, existing reviews, issue comments, inline comments and review threads. Routed skill: review-comet-pr. No sibling skill applies to this CI-only change.
Exact-head CI: 19 checks passed, five failed and 14 were skipped. In Comet CI run 35516961938, all four Java lint rows failed on the untracked log, which also failed Required Checks. Preflight, including actionlint, passed. Verified that tested merge 3f73798031d96bfa758bcb360750b1e5a3b9a2e3 has the requested base and head as parents.
Validation: python3 dev/ci/check-ci-config.py passed. Twenty-eight disposable Bash cases using the four matrix rows’ Maven arguments verified success, recovery, lint failure, exhaustion and compiler-failure behavior. All 12 successful cases reproduced the downstream dirty-tree failure. Maven and sleep were stubbed, so these tests establish control flow rather than real network recovery or JVM execution. No local Maven/JVM build, Spark suite or actionlint run was performed. Project files and GitHub state remain unchanged.
|
Sure, Andy. I reviewed the change and put a runnable demonstration on a branch of my fork, based on this PR's head. Instructions are here. The reproducer uses Maven 3.9.6 against a local HTTP repository returning 503. For both Scala 2.12 and 2.13, the short This matters when narrowing the retry condition to recognized transient transfer errors. My recommendations are:
The published branch contains the reproducer and instructions. If this approach looks good to you, I can follow up with the workflow patch and regression coverage. |
… retries Address review feedback on the retry loop: - Write the captured log to a mktemp file instead of the checkout, so a successful run no longer fails the clean-tree check on scalafix-check.log. - Retry only when Maven's error names a download (No plugin found for prefix, could not be resolved, Could not transfer, or the scala.tools.nsc.Main ClassNotFoundException a missing compiler jar produces). A lint violation or a compile error fails on the first attempt. Replayed against 111 Lint Java step logs from CI since 2026-09-20, this retries exactly the six download failures and none of the 30 compile errors or 25 lint violations. - Pass -U on retries. Two of the five plugin failures since 09-20 were a 404 from Central, and Maven caches a 404 in the local repository, so a plain retry fails the same way until the update interval elapses. - Run the step under bash. The container default is sh (dash), where $RANDOM is empty and the jitter was silently zero.
|
Thanks @ErikBPF, the reproducer helped. Since the PR was already open I pushed the workflow change myself in 49ff6ce, so no need for a follow-up patch. It writes the log to a I kept the short The 404 case turned out to matter. Since this PR was opened the lint has failed this way five more times: 403s on the merge-queue runs for #6065 and #6541, a 429 on the merge-queue run for #6647 after the resolver's own six retries, and 404s on two PR runs. Maven caches a 404 in the local repository, so the original loop would have failed all four attempts on those. I confirmed that with Maven 3.9.6 against a @kazuyukitanimura yes, still active. Those three merge-queue evictions are why. |
Which issue does this PR close?
Closes #6060.
Rationale for this change
lint-java'sRun scalafix checkstep is a single unguarded./mvnw ... scalafix:scalafixinvocation, so a Maven Central hiccup while resolving the plugin fails the job exactly the way a real lint violation does. Maven ends withNo plugin found for prefix 'scalafix', and the cause only shows up in aFailed to retrieve plugin descriptorwarning above it. That first happened on #6035. Between 2026-09-27 and 2026-10-10 it happened five more times, three of them on merge-queue runs (for #6065, #6541 and #6647), where a failed lint row evicts the pull request. Central answered 403 twice, 429 once (after the six resolver retries that.mvn/maven.configconfigures), and 404 twice.The
scalafix-syntacticjob directly above already guards against this: it retries the download and then lints offline, "so a nonzero exit can only mean a lint violation, never a download failure".lint-javahad no equivalent guard, and.github/actions/maven-bootstrapretries only the Maven distribution download, not resolution during the build.The offline half of that trick does not transfer to Maven here, because the build resolves plugin and compiler artifacts as it goes, so a
-osecond pass would turn a cold cache into a hard failure. Retrying is the part that transfers, provided a genuine violation or compile error still fails on the first attempt. A plain retry is not enough for the 404 case, though. Maven caches a not-found result in the local repository and will not ask Central again until the update interval elapses, unless the build passes-U.What changes are included in this PR?
Run scalafix checkretries the Maven invocation up to four times, with the same backoff as.github/actions/maven-bootstrap(10s, 20s, 40s plus 0-4s of jitter) and the same::error::/::warning::reporting.[ERROR]lines names a download:No plugin found for prefix,could not be resolved,Could not transfer, orClassNotFoundException: scala.tools.nsc.Main, which is how a scala-compiler jar that did not download surfaces (seen on feat: use native shuffle for row input by converting it to Arrow #6607). A lint violation, a compile error or any other failure fails on the first attempt.-U, so a 404 that the failed attempt cached is checked again.mktempfile outside the checkout, so a passing run leaves the working tree clean forcheck-working-tree-clean.sh.shell: bash. The container default issh(dash), where$RANDOMis empty.How are these changes tested?
Run scalafix checkstep logs from CI runs between 2026-09-20 and 2026-10-10. It retries all six download failures and none of the 30 compile errors or 25 lint violations. The other 50 logs passed and never reach it.bash --noprofile --norc -e -o pipefail, the way Actions runsshell: bash, with real Maven 3.9.6 resolvingscalafix-maven-pluginthrough afile://mirror with injected faults. A one-off 404 recovers on the second attempt with-U, where the earlier version of this loop failed all four attempts withwas cached in the local repository. A one-off transfer error recovers on the second attempt, and a persistent 404 stops after four attempts with the download error. Replayed CI logs for a lint violation and a compile error fail after one Maven run with no sleep.actionlint -color --shellcheck=off, the invocation thePreflightjob runs, andpython3 dev/ci/check-ci-config.pypass.Lint Javarows on this pull request.