Skip to content

Fix concurrent builder race conditions in artifact resolution and phase ordering - #12680

Draft
gnodet wants to merge 2 commits into
masterfrom
run-maven-4-0-x-on-a-few-projects-let-s-start-wit
Draft

Fix concurrent builder race conditions in artifact resolution and phase ordering#12680
gnodet wants to merge 2 commits into
masterfrom
run-maven-4-0-x-on-a-few-projects-let-s-start-wit

Conversation

@gnodet

@gnodet gnodet commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes race conditions in Maven 4.x concurrent builder (-b concurrent) that cause NPEs during artifact resolution. Verified with a full Apache Camel concurrent build (-b concurrent -T1C -Dquickly).

Root Cause

The concurrent builder can leave resolved dependency artifacts with null files in two cases:

  1. Reactor projects: not yet packaged when a downstream module starts compiling (Artifact.getFile() returns null)
  2. External dependencies: the Aether resolver races in the concurrent builder, returning artifacts that were collected but not fully resolved (file not set)

Plugins like maven-compiler-plugin 3.15.0 call getCompileClasspathElements() which iterates over dependency artifacts and calls artifact.getFile().getPath() — NPE when the file is null.

Changes

LifecycleDependencyResolver.java — Three fallback mechanisms for null-file artifacts:

  1. Output directory fallback (existing, enhanced): for reactor projects without packaged artifacts, use the compiled output directory (target/classes)
  2. GAV-based reactor fallback (new): when Artifact.equals() misses due to type/classifier mismatch, match by groupId:artifactId:version
  3. Local repository fallback (new): for non-reactor artifacts with null files, look up the file in the local repository via LocalRepositoryManager.getPathForLocalArtifact()

BuildPlanExecutor.java — Maven 3 personality conditional:

  • When maven.maven3Personality is enabled, enforce sequential SOURCES → RESOURCES ordering for backward compatibility with plugins that assume Maven 3 phase ordering
  • V4-native plugins should use @After annotations to declare ordering dependencies

DefaultLifecycleRegistry.java — Revert RESOURCES ordering:

  • Remove after(SOURCES) constraint from the lifecycle definition (moved to a runtime conditional in BuildPlanExecutor gated on Maven 3 personality)

Testing

  • Full Apache Camel concurrent build: zero NPEs (the only failure is a pre-existing CXF codegen issue unrelated to Maven)
  • Previous builds confirmed each fallback mechanism independently

Test plan

  • Full Camel concurrent build passes with zero NPEs
  • CXF codegen failure confirmed as pre-existing (reproduces with standard single-threaded builder)
  • CI passes

🤖 Generated with Claude Code

gnodet and others added 2 commits August 5, 2026 05:26
…se ordering

The concurrent builder (-b concurrent) suffered from several race conditions
when building large multi-module projects like Apache Camel (682 modules):

1. Thread-safety for session.getCurrentProject(): Multiple executor threads
   shared MavenSession.currentProject, causing dependency resolution and
   artifact filter to use the wrong project. Fixed by adding a ThreadLocal
   in the concurrent MojoExecutor and recording lifecycle phases directly
   on the correct project (not via the racy session reference).

2. MavenProject artifact state thread-safety: setResolvedArtifacts(),
   setArtifactFilter(), getArtifacts(), and getArtifactMap() were not
   thread-safe. Made them synchronized with merge semantics that preserve
   artifact file references across concurrent resolutions.

3. Reactor output directory fallback: When a reactor dependency has been
   compiled but not yet packaged, use its output directory (if it exists)
   as the artifact file. This prevents NPEs in downstream compiler plugin
   classpath resolution.

4. V4 lifecycle RESOURCES ordering: Added after(SOURCES) constraint to
   the RESOURCES phase so resource processing happens after source
   generation, matching Maven 3 sequential ordering.

5. after:* step skip propagation: Changed the after:* step decision from
   checking before:* status to checking the phase step itself. Before:*
   is an empty lifecycle setup step that always completes early; checking
   it allowed after:* steps to execute when the phase was SKIPPED,
   causing downstream steps to run out of order.

6. Scope expansion for dependency ordering: filterByScope() now expands
   "compile" scope to include provided and system dependencies, matching
   Maven dependency resolution semantics and ensuring provided-scope
   reactor dependencies are properly ordered.

Tested with Apache Camel 682-module build: reduced concurrent builder
failures from 67 modules to 0 (remaining 3 failures are pre-existing
Maven 4 issues unrelated to the concurrent builder).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l-file fallbacks

Address review feedback on the concurrent builder patch:

- Revert MavenProject.java to upstream: remove synchronized keywords and
  merge logic from setResolvedArtifacts/setArtifactFilter/getArtifacts.
  MavenSession.currentProject is already a ThreadLocal in Maven 4, so
  synchronization is unnecessary.

- Revert RESOURCES ordering in DefaultLifecycleRegistry: remove
  after(SOURCES) constraint. V4-native plugins should use @after
  annotations to declare ordering dependencies explicitly.

- Add Maven 3 personality conditional in BuildPlanExecutor: when
  maven.maven3Personality is enabled, enforce sequential SOURCES →
  RESOURCES ordering for backward compatibility with plugins that rely
  on Maven 3 phase ordering.

- Add null-file artifact fallbacks in LifecycleDependencyResolver:
  1. GAV-based reactor lookup when Artifact.equals() misses due to
     type/classifier mismatch between resolved dependency and reactor
     artifact.
  2. Local repository lookup for non-reactor artifacts whose files were
     not set by the Aether resolver (race condition in concurrent
     builder where artifacts are collected but not fully resolved).

These fallbacks eliminate NPEs in the compiler plugin when it calls
getCompileClasspathElements() on artifacts with null files during
concurrent builds. Verified with a full Apache Camel concurrent build
(-b concurrent -T1C -Dquickly): zero NPEs across all modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant