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]
