feat(maven): add batch executor for multi-task Maven execution#33228
feat(maven): add batch executor for multi-task Maven execution#33228FrozenPandaz merged 5 commits intomasterfrom
Conversation
✅ Deploy Preview for nx-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
View your CI Pipeline Execution ↗ for commit 1adecf5
☁️ Nx Cloud last updated this comment at |
030d9e0 to
6b7b8c4
Compare
a5fdb17 to
0cf765b
Compare
11b5c2e to
8491549
Compare
|
Failed to publish a PR release of this pull request, triggered by @FrozenPandaz. |
🐳 We have a release for that!This PR has a release associated with it. You can try it out using this command: npx create-nx-workspace@0.0.0-pr-33228-f4ef600 my-workspaceOr just copy this version and use it in your own command: 0.0.0-pr-33228-f4ef600
To request a new release for this pull request, mention someone from the Nx team or the |
🐳 We have a release for that!This PR has a release associated with it. You can try it out using this command: npx create-nx-workspace@0.0.0-pr-33228-3422425 my-workspaceOr just copy this version and use it in your own command: 0.0.0-pr-33228-3422425
To request a new release for this pull request, mention someone from the Nx team or the |
|
Failed to publish a PR release of this pull request, triggered by @FrozenPandaz. |
🐳 We have a release for that!This PR has a release associated with it. You can try it out using this command: npx create-nx-workspace@0.0.0-pr-33228-ece8d5d my-workspaceOr just copy this version and use it in your own command: 0.0.0-pr-33228-ece8d5d
To request a new release for this pull request, mention someone from the Nx team or the |
| // Thread-safe queue of ready tasks and graph state | ||
| val taskQueue = LinkedBlockingQueue<String>(initialGraph.roots) | ||
| val graphRef = AtomicReference(initialGraph) | ||
| val successfulTasks = ConcurrentHashMap<String, Boolean>() |
There was a problem hiding this comment.
Why use three maps here instead of one, we could keep a reference to some state instead of three booleans across three maps?
| javaArgs.push('--verbose'); | ||
| } | ||
|
|
||
| if (process.env.NX_VERBOSE_LOGGING === 'true') { |
There was a problem hiding this comment.
nit: do we need two if statements here
| repeat(numWorkers) { | ||
| executor.submit { | ||
| while (true) { | ||
| val taskId = taskQueue.poll() ?: break |
There was a problem hiding this comment.
Could you have a case where the queue is empty, but more work just hasn't been added to it yet?
| arguments.add("-e") | ||
| } | ||
|
|
||
| arguments.add("-nsu") |
There was a problem hiding this comment.
Do we ever want to not have this?
| attachedCount++ | ||
| } | ||
| } else { | ||
| log.warn(" Cannot attach artifact (no MavenProjectHelper): ${file.name}") |
There was a problem hiding this comment.
This kind of error is fine just leaving as warnings?
There was a problem hiding this comment.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| if (taskId == null) { | ||
| log.debug("Worker $workerId exiting loop (poll returned null)") | ||
| break | ||
| } |
There was a problem hiding this comment.
Bug: Workers exit prematurely when task queue temporarily empties
The worker loop uses non-blocking poll() and exits immediately when the queue is empty. This causes a race condition: if there are fewer initial roots than workers, or if the queue empties while tasks are being processed, workers exit permanently and never process newly available tasks. For example, with 8 workers and 1 initial root, 7 workers exit immediately and all subsequent work is done sequentially by a single worker. This was flagged in the PR discussion ("Could you have a case where the queue is empty, but more work just hasn't been added to it yet?"). The workers need to wait or check the completion state before exiting rather than breaking out of the loop on first empty poll.
| // Track current project across invocations to avoid duplicate "Building" headers | ||
| @Volatile | ||
| private var currentProjectId: String? = null | ||
| } |
There was a problem hiding this comment.
Bug: Static state causes race condition across concurrent executions
The currentProjectId is stored in a companion object (static) and shared across all BatchExecutionListener instances. Since NxMaven.execute() creates new listener instances per invocation, and multiple worker threads call execute() concurrently from MavenInvokerRunner, the static variable creates a race condition. The check-then-update in projectStarted (if (projectId != currentProjectId) { currentProjectId = projectId; ... }) is not atomic, leading to incorrect suppression or duplication of "Building" headers when projects execute in parallel.
Additional Locations (1)
| readers.add(reactorReader) | ||
| request.workspaceReader?.let { readers.add(it) } | ||
| ideWorkspaceReader?.let { readers.add(it) } | ||
| cachedWorkspaceReader!!.setReaders(readers) |
There was a problem hiding this comment.
Bug: Concurrent modification of shared workspace reader without synchronization
The executeWithCachedGraph method is called concurrently from multiple worker threads in MavenInvokerRunner, but it modifies the shared cachedWorkspaceReader by calling setReaders() without synchronization. Each thread creates its own reactorReader via lookup and then overwrites the shared workspace reader's readers set. When threads race, one thread's readers configuration gets overwritten by another's, potentially causing incorrect artifact resolution during parallel Maven executions.
| ) | ||
| } catch (e: Exception) { | ||
| log.warn(" ⚠️ Failed to initialize ModelBuilderSession: ${e.message}", e) | ||
| } |
There was a problem hiding this comment.
Bug: Concurrent modification of shared InternalSession data causes race
The initializeModelBuilderSession method modifies the session data of the shared cachedInternalSession by calling getData().set(). Since cachedInternalSession is reused across all concurrent executions (set via session.session = cachedInternalSession), and executeWithCachedGraph is called without synchronization from multiple worker threads, concurrent invocations overwrite each other's ModelBuilderSession. This race condition could cause Maven plugins that rely on the ModelBuilderSession (like install:install) to fail intermittently with NPEs or use incorrect session data.
Additional Locations (1)
There was a problem hiding this comment.
Nx Cloud has identified a possible root cause for your failed CI:
Our investigation confirms this test failure exists in master with identical patterns, indicating a pre-existing issue rather than a regression from the Maven batch executor changes. The PR exclusively modifies Maven-related code (batch-runner, maven-plugin, executors) with no changes to web component generation, SWC compiler configuration, or TypeScript decorator handling that could affect this test.
No code changes were suggested for this issue.
If the issue was transient, you can trigger a rerun:
🎓 Learn more about Self-Healing CI on nx.dev
Initial implementation of Maven batch execution framework for efficient multi-task execution: - Add batch executor for parallel execution of multiple Maven tasks - Implement project selector to filter and target specific projects - Parse TaskGraph JSON into typed Kotlin data classes for type safety - Configure invoker with workspace pom file and repository settings - Upgrade batch executor to Maven 4.x EmbeddedMavenExecutor API - Add Nx Maven batch-runner module with CLI argument parsing - Create executor implementations for Maven task invocation - Add TypeScript/JavaScript executor schemas and implementations - Support configurable Maven execution with proper error handling
…o 4.0.0-rc-5 - Update all pom.xml files from version 0.0.10 to 0.0.11 - Upgrade Maven 4 dependencies in batch-runner from rc-4 to rc-5 - Update e2e test to use Maven 4.0.0-rc-5 - Add rc-5 paths to MavenHomeDiscovery for better version detection
| * OutputStream that writes to both a capture buffer and streams to stdout in real-time. | ||
| * (stdout is inherited by parent process, stderr is used for result JSON lines) | ||
| */ | ||
| class TeeOutputStream( |
There was a problem hiding this comment.
Is this actually used anywhere?
|
This pull request has already been merged/closed. If you experience issues related to these changes, please open a new issue referencing this pull request. |

Summary
Adds a batch executor for Nx Maven that enables parallel multi-task execution with significant performance improvements. The batch runner keeps Maven resident in memory, avoiding cold start overhead for each task.
Changes
1. Batch Runner JAR (
packages/maven/batch-runner)ResidentMavenInvokerto keep Maven in memoryjar:jarare visible toinstall:install2. TypeScript Executors (
packages/maven/src/executors/maven)mvnw/mvn3. Shared Utilities (
packages/maven/shared)BuildState,BuildStateApplier,BuildStateRecorderfor cross-invocation stateMavenCommandResolverfor detecting Maven executable4. Maven Plugin Updates (
packages/maven/maven-plugin)@nx/maven:mavenexecutor (batch-aware)GitIgnoreClassifierfor nested .gitignore handlingPerformance
Version Support
Testing
Current Behavior
Uses subprocess execution via
mvnw/mvnfor each task.Expected Behavior
Batch execution keeps Maven resident, dramatically reducing per-task overhead.
Related Issue(s)
Part of Maven integration improvements.