-
fix(gradle): prevent Gradle and Maven daemon accumulation during project graph recalculation (#35143)
发布于
2026-04-02 19:27:56 +00:00 Current Behavior
When file changes trigger rapid project graph recalculations (e.g.,
during development withnx graphor a dev server running), each call
topopulateProjectGraphspawns a new Gradle process via
execGradleAsync. If the previous Gradle daemon is still busy
processing the prior request, Gradle spawns a new daemon. These
daemons persist for 3 hours by default (Gradle's idle timeout), leading
to dozens of orphanedjava.exeprocesses consuming significant memory.Maven has a similar issue —
runMavenAnalysisspawns a long-lived
process with no timeout or cancellation support at all.Root Cause Analysis
The daemon explosion happens due to three compounding issues:
-
No cancellation of in-flight processes: When a newer project
graph request arrives, the previous invocation continues running. Each
concurrent invocation finds the existing daemon busy and spawns a new
one. -
Windows process tree issue: On Windows,
execFilewithshell: truerunscmd.exe → gradlew.bat → java.exe. Node'sAbortSignalonly
terminatescmd.exe(the immediate child process), leavingjava.exe
running as an orphan. -
No timeout for Maven: Maven analysis could run indefinitely with
no way to cancel or time out.
Reproduction
- Run
nx graphin a workspace with@nx/gradleregistered - Rapidly modify a
build.gradle.ktsfile (e.g.,while true; do echo "// tick" >> build.gradle.kts; sleep 0.01; done) - Watch
java.exeprocesses accumulate:tasklist | grep java
(Windows) orps aux | grep java(Unix) - Without this fix: 15+ Gradle daemons within seconds, persisting
for 3 hours each - With this fix: 1 Gradle daemon remains stable under the same
conditions
Expected Behavior
When rapid file changes trigger multiple project graph recalculations:
- The previous invocation is cancelled before starting a new one
- Cancelled calls that have already spawned a process get their entire
process tree killed (not just the shell wrapper) - Only 1 daemon remains active at any time, rather than accumulating
dozens - Both Gradle and Maven have configurable timeouts with clear error
messages
Changes
Gradle
1. Self-contained cancellation in
get-project-graph-lines.tsMoved the
AbortControllerfrom
get-project-graph-from-gradle-plugin.tsinto
get-project-graph-lines.ts, closer to where processes are spawned.
getNxProjectGraphLinesnow manages its own abort controller —
cancelling any in-flight request before starting a new one. Uses
abort('cancelled')reason to distinguish external cancellation from
timeout.2. Tree-kill on abort (
exec-gradle.ts)Instead of passing the
AbortSignaldirectly to Node'sexecFile
(which only kills the immediate child process), we intercept the signal
and usetree-killto terminate the entire process tree. This ensures
java.exeis killed along withcmd.exeandgradlew.baton Windows.Maven
3. Timeout and cancellation support for
maven-analyzer.tsAdded the same abort controller + tree-kill + timeout pattern to Maven
analysis:- Configurable timeout via
NX_MAVEN_ANALYSIS_TIMEOUTenv var (default:
120s local, 600s CI) cancelPendingMavenAnalysis()cancels in-flight processes on repeated
callstree-killensures the entire Maven process tree is killed on abort- Clear timeout error messages with actionable steps
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
下载附件
-