>I am by no means familiar with this part of Maven, but would it be an option to place "clean" and "install" in separate TaskSegments to avoid the race condition completely?
Yes, I was talking about the same. There was a comment regarding it that "compile 4.x+ plugin will compile the code of all modules", but I have very strong concerns it's how actually the thing work. "mvn clean install -T4" now executes all phases of clean lifecycle + all phases of install lifecycle (subset of "default" lifecycle) in a list of mojos, in a single foreach loop. Instead, it should run clean lifecycle for all modules, and only then run install lifecycle for all modules. For example, Gradle does it this way and I find this more correct. On Wed, Aug 5, 2026 at 10:49 AM Maarten Mulders <[email protected]> wrote: > Hi Guillaume, > > Your problem statement seems clear to me, but from the three approaches, > it seems to me all three target a consequence of the root cause. > > Since clean and install are placed into the same TaskSegment [...] > > I am by no means familiar with this part of Maven, but would it be an > option to place "clean" and "install" in separate TaskSegments to avoid > the race condition completely? If I understand correctly, it would also > make it practically impossible to "clean" something that was "install"ed > just (the ordering problem when using a lock-based approach). > > Apart from that, I would not be a big fan of a solution that moves the > project-local-repo *outside* the project directory (approach 2). > > Thanks, > > Maarten > > On 03/08/2026 14:09, Guillaume Nodet wrote: > > Hi all, > > > > I'd like to get your input on the fix for GH-12646 — a race condition > with > > project-local-repo during parallel clean install builds. > > > > The problem > > > > When the root pom.xml has a parent that is also part of the reactor > (e.g. a > > super-pom), MultiThreadedBuilder schedules the parent first, then runs > the > > root project and child modules concurrently. Since clean and install are > > placed into the same TaskSegment, there is no barrier between them — the > > root project's maven-clean-plugin can delete target/ while sibling > modules > > are concurrently writing artifacts into target/project-local-repo, > causing > > the build to fail. > > > > Approaches considered > > > > 1. Lock-based synchronization (ReentrantReadWriteLock in ReactorReader) > > > > Acquire a write lock when the project owning target/ enters its clean > > phase, and a read lock when installing artifacts. This prevents the crash > > (concurrent access) but does not guarantee ordering — a module could > > install artifacts before the root's clean starts, only to have them > wiped. > > It also adds complexity to ReactorReader for what is fundamentally an > > architectural problem. > > > > 2. Move project-local-repo to ~/.m2/ (user home) > > > > E.g. ~/.m2/local-repository/${projectName}_${hash}, similar to how > IntelliJ > > stores project caches. This avoids both the race and the git concern, > but: > > - Hard links (already used by ReactorReader) cannot cross filesystem > > boundaries — if ~/.m2/ is on a different volume, every artifact becomes a > > full copy > > - Stale directories accumulate when projects are deleted/moved, with no > > cleanup mechanism > > - CI containers often share ~/.m2/ across builds, causing unwanted > > accumulation > > - Loses project locality (harder to inspect/debug) > > > > 3. Move project-local-repo to .mvn/project-local-repo (chosen) > > > > Move the directory from target/project-local-repo to > > .mvn/project-local-repo. Since maven-clean-plugin only deletes target/, > the > > race becomes structurally impossible. ReactorReader fully owns the > > lifecycle — per-GAV cleanup when a project enters its clean phase, > install > > on project success. The existing hard-link optimization continues to work > > since both directories are on the same filesystem. > > > > The trade-off is that .mvn/project-local-repo needs to be gitignored. > This > > follows the Gradle convention where .gradle/ at the project root is > > universally in .gitignore templates. Maven could also document this > > convention or consider auto-appending the entry. > > > > The fix itself is minimal — the core change is a single line in > > getProjectLocalRepo(), and the rest is removing the lock infrastructure > > (net -40 lines). > > > > 4. Keep in target/ but use maven-clean-plugin fast mode > > > > The fast clean option (maven.clean.fast=true, since plugin 3.2) > atomically > > renames target/ instead of recursively deleting it. This would likely > avoid > > the race, but it's opt-in and not the default — users hitting the bug > would > > need to know about it. > > > > FWIW, a PR has been raised on m-clean-p master branch (4.x) to refactor > the > > fast cleaner (fixing problems) and make it the default. > > > > Open questions > > > > - Is .mvn/ the right location, or should we consider a different > > project-root directory? > > - Should Maven auto-add .mvn/project-local-repo to .gitignore, or > document > > it as a convention? > > - Are there other considerations I'm missing? > > > > PR: https://github.com/apache/maven/pull/12650 > > Issue: https://github.com/apache/maven/issues/12646 > > > > Looking forward to your thoughts. > > > > Cheers > > Guillaume Nodet > > > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: [email protected] > For additional commands, e-mail: [email protected] > >
