Skip validation and decoration on re-entrant RepositorySystem calls#1957
Skip validation and decoration on re-entrant RepositorySystem calls#1957gnodet wants to merge 2 commits into
Conversation
Maven 4's ArtifactDescriptorReader -> ModelBuilder -> ModelResolver
chain re-enters RepositorySystem during collectDependencies. This
breaks the resolver's single-crossing contract: validation rejects
intermediate state (e.g. uninterpolated ${...} expressions from
transitive POMs), and artifact decorators run redundantly.
Re-entrancy can occur on the same thread or on pool threads used by
BfDependencyCollector's SmartExecutor for parallel descriptor
resolution. To handle both cases, we stamp a sentinel marker into
the RequestTrace on the outermost call. On re-entry, isReentrant()
walks the trace ancestry looking for the marker — if found,
validation and decoration are skipped.
This uses the existing RequestTrace infrastructure which is already
propagated across threads by the caller (Maven's model builder
explicitly copies traces to pool threads), requiring no ThreadLocal
or session-scoped state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds re-entrancy detection to DefaultRepositorySystem so that when Maven 4 re-enters RepositorySystem during an in-flight resolution (via RequestTrace propagation), the inner calls skip session/request validation and artifact decoration to avoid rejecting intermediate model states and to prevent decorator-induced corruption.
Changes:
- Introduces a
RequestTracesentinel marker andisReentrant(...)traversal to detect nestedRepositorySysteminvocations. - Gates validation (and, for
readArtifactDescriptor, decoration) on the outermost call for several publicRepositorySystemmethods. - Adds
DefaultRepositorySystemReentrancyTestcovering outermost vs re-entrant validation andreadArtifactDescriptordecoration behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRepositorySystem.java |
Adds RequestTrace-based re-entrancy detection and conditionally skips validation/decoration on inner calls. |
maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemReentrancyTest.java |
Adds unit tests validating the intended re-entrancy behavior and the reported Maven 4 intermediate-state scenario. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| RequestTrace firstTrace = | ||
| requests.stream().map(ArtifactRequest::getTrace).findFirst().orElse(null); | ||
| if (!isReentrant(firstTrace)) { | ||
| validateSession(session); | ||
| repositorySystemValidator.validateArtifactRequests(session, requests); |
There was a problem hiding this comment.
Good catch — fixed in eb1e2af. resolveArtifacts() now stamps REPOSITORY_SYSTEM_CALL into each request's trace on outermost calls, matching the single-request methods.
There was a problem hiding this comment.
Good catch — fixed in eb1e2af. resolveArtifacts() now stamps REPOSITORY_SYSTEM_CALL into each request's trace on outermost calls, matching the single-request methods.
| RequestTrace firstTrace = | ||
| requests.stream().map(MetadataRequest::getTrace).findFirst().orElse(null); | ||
| if (!isReentrant(firstTrace)) { | ||
| validateSession(session); | ||
| repositorySystemValidator.validateMetadataRequests(session, requests); |
There was a problem hiding this comment.
Same fix applied in eb1e2af — resolveMetadata() now also stamps the marker into each request's trace on outermost calls.
resolveArtifacts() and resolveMetadata() now stamp the REPOSITORY_SYSTEM_CALL marker into each request's trace on outermost calls, matching the single-request methods. This ensures downstream re-entrant calls triggered during resolution can detect the marker. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gnodet
left a comment
There was a problem hiding this comment.
Review Summary
Well-designed re-entrancy detection using RequestTrace ancestry instead of ThreadLocal, correctly handling both same-thread and cross-thread re-entry.
What's good
-
Design choice: Using
RequestTraceancestry instead ofThreadLocalis the right call.ThreadLocalwould break whenBfDependencyCollectordispatches work to pool threads (a known re-entrancy path).RequestTraceis explicitly propagated across threads by callers like Maven's model builder, making it the correct carrier for this marker. -
Sentinel pattern: The private
static finalsentinel with identity comparison (==) is clean and avoids accidental collisions with user data in the trace chain. -
Documentation: The Javadoc on
REPOSITORY_SYSTEM_CALLis excellent — it explains the architectural assumption, why it breaks, the two re-entrancy paths (same-thread and cross-thread), and whyRequestTraceis the right mechanism. This documentation is invaluable for future maintainers. -
resolveDependenciescorrectness: When callingdependencyCollector.collectDependenciesdirectly (bypassing the public facade), the trace passed to theCollectRequestalready carries the marker in its ancestry, so downstream re-entry through the public facade is correctly detected.
Minor observations (non-blocking)
-
Batch trace assumption (line ~280): The comment "All requests in a batch share the same trace context, so checking any one is sufficient" is a stated assumption but is not enforced. In practice this always holds since batch requests are constructed within a single operation, but a defensive check could prevent subtle bugs for future consumers.
-
Null-check ordering (line ~222):
requireNonNull(request)now runs beforevalidateSession(session)on outermost calls (previously session was validated first). This is necessary for the re-entrancy check but changes which exception is thrown when both are null. Negligible impact. -
Shutdown on re-entry (line ~580): On re-entrant calls,
validateSystem()is also skipped since it's insidevalidateSession(). Ifshutdown()is called mid-operation, a re-entrant call would proceed. This is an extremely unlikely race and consistent with the principle that the outermost call owns the lifecycle contract.
Looks good overall. The tests cover the key single-threaded scenarios. Cross-thread re-entrancy isn't explicitly tested, but since the mechanism is purely trace-chain-based (no thread affinity), the single-threaded tests are sufficient to validate the logic.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Claude Code on behalf of Guillaume Nodet
|
Thanks for the thorough review! Addressing the observations:
Regarding cross-thread testing: as you noted, the mechanism is purely trace-chain-based with no thread affinity, so the single-threaded tests are sufficient. The A companion PR for Maven will follow to remove the consumer-side dependency filtering workarounds that are no longer needed with this fix. |
Summary
Maven 4's
ArtifactDescriptorReader→ModelBuilder→ModelResolverchain re-entersRepositorySystemduringcollectDependencies. This breaks the resolver's single-crossing contract:MavenValidator(registered via theValidatorFactorySPI) rejects uninterpolated${...}expressions that are valid intermediate state in transitive POMs during model buildingInvalid Collect Request: nullHow it works
On the outermost call to any
RepositorySystempublic method, a sentinel marker is stamped into the request'sRequestTrace. On re-entry (whether on the same thread or a pool thread),isReentrant()walks the trace ancestry — if the marker is found, validation and decoration are skipped.This leverages the existing
RequestTraceinfrastructure which is already propagated across threads by callers (Maven's model builder explicitly copies traces to pool threads viasession.setCurrentTrace(trace)), requiring no ThreadLocal or session-scoped state.Changes
DefaultRepositorySystem: all public resolution methods checkisReentrant(trace)before validating/decoratingreadArtifactDescriptor: additionally skips artifact decoration on re-entryinstall,deploy,newResolutionRepositories,newDeploymentRepository,flattenDependencyNodes) always validate — they are terminal operations that don't participate in re-entrancyDefaultRepositorySystemReentrancyTestwith 5 tests covering:readArtifactDescriptorskips decorationTest plan
maven-resolver-impl🤖 Generated with Claude Code