cmunier opened a new pull request, #13794:
URL: https://github.com/apache/maven/pull/13794

   `MavenSession` keeps the current project in a `ThreadLocal` created with 
`ThreadLocal.withInitial(() -> topLevelProject)`, and `clone()` gives every 
clone a new `ThreadLocal.withInitial(() -> current)`. With `withInitial`, a 
plain `get()` on a thread that has no value yet stores the initial value in 
that thread's `ThreadLocalMap`. So every thread that only *reads* 
`getCurrentProject()` keeps a strong reference to the default project. 
`clone()` itself reads the current project of the original session, so cloning 
a session on a pool thread is enough to leave such an entry behind.
   
   When the session is discarded, the `ThreadLocal` key is only weakly 
reachable, but the value is not: the stale entry keeps the `MavenProject` 
reachable as long as the thread lives and does not expunge it. A `MavenProject` 
reaches the whole build (`ProjectBuildingRequest` → `RepositorySystemSession` → 
`ReactorReader` → `MavenSession` → plugin contexts → plugin realms).
   
   In a plain `mvn` run this does not matter, because the JVM ends with the 
build. In a long-lived JVM like the Maven Daemon (mvnd), threads such as 
`ForkJoinPool.commonPool` workers outlive the build and keep earlier builds 
reachable. I found this while analysing metaspace growth of an mvnd daemon with 
Tycho, which clones the session on `commonPool` workers (see 
eclipse-tycho/tycho#6471). Tycho may reset the current project of its clones 
(eclipse-tycho/tycho#6472), but it cannot avoid the entries created by the 
reads in `MavenSession` itself: with that fix, the remaining entries on the 
`commonPool` workers hold exactly the default project of the original session 
(see the measurement below).
   
   This PR keeps the default project in a field and uses the `ThreadLocal` only 
for projects that are explicitly set. Reading the current project on a thread 
without a value now returns the default without storing it. The behavior is 
unchanged:
   
   - the default is still the top-level project, or for a clone the current 
project of the cloning thread,
   - `setCurrentProject(null)` still yields `null` (stored as 
`Optional.empty()`), e.g. after `LifecycleModuleBuilder` has built a project,
   - setting the current project on a clone still does not write through to the 
original session, and `setProjects` still resets the projects of all threads.
   
   The new test 
`MavenSessionTest#testReadingCurrentProjectOnOtherThreadDoesNotRetainProject` 
reads the current project of a session and of a clone on another thread, 
discards the session and checks with a `WeakReference` that the project can be 
collected. It fails without the change. The existing tests for the current 
project (default, `null`, thread isolation, clones) pass unchanged, as do all 
other tests of `maven-core`.
   
   I also checked the effect in mvnd 1.0.6 (Maven 3.9.16, JDK 21) with the same 
change applied to `MavenSession` of Maven 3.9.16: a reactor with a parent and 8 
`eclipse-plugin` modules (Tycho 6.0.0-SNAPSHOT), `mvnd -o compile` twice in a 
fresh daemon, then a full GC and a small Java agent that lists all 
`ThreadLocalMap` entries whose value is a `MavenProject` (directly or wrapped 
in an `Optional`). All entries found were stale and on 
`ForkJoinPool.commonPool` workers:
   
   | Tycho | Maven | Entries with a bundle project | Entries with the parent 
project |
   |---|---|---|---|
   | without eclipse-tycho/tycho#6472 | 3.9.16 | 9 | 9 |
   | without eclipse-tycho/tycho#6472 | 3.9.16 + this change | 9 | 1 |
   | with eclipse-tycho/tycho#6472 | 3.9.16 | 0 | 8 |
   | with eclipse-tycho/tycho#6472 | 3.9.16 + this change | 0 | 0 |
   
   The 8 entries removed by this change are the default project stored by the 
reads. The remaining entries without the Tycho fix are projects that Tycho 
explicitly set on its clones (the parent is one of the reactor projects). With 
both changes, no entries were left, also after 4 builds.
   
   Callers that set the current project on a clone in another thread still have 
to reset it (as Tycho does now); otherwise the clone's `ThreadLocal` keeps that 
project, as before.
   
   **Backport:** the same code is in `maven-4.0.x` and in 
`maven-3.10.x`/`maven-3.9.x` (`maven-core`). Could this please be backported to 
`maven-4.0.x` and `maven-3.10.x`? I'm happy to open the backport PR for 
`maven-3.10.x`. mvnd 1.x embeds Maven 3.9.x, so `maven-3.9.x` would help mvnd 
users as well.
   
   AI disclosure: drafted with Claude Code (Anthropic Claude Opus 5.5, 
interactive mode). I have reviewed and tested the change myself.
   
   ---
   
   - [x] I hereby declare this contribution to be licensed under the [Apache 
License Version 2.0, January 2004](https://www.apache.org/licenses/LICENSE-2.0)
   - [ ] I hereby declare that I have filed an [Apache Individual Contributor 
License Agreement](https://www.apache.org/licenses/icla.pdf).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to