Extend re-entrancy detection for broken trace chains#2009
Conversation
…naged dependency validation The trace-based re-entrancy detection added in PR #1957 does not cover all code paths. Maven 4's RequestTraceHelper converts between Maven API traces and resolver RequestTrace objects, creating a fresh trace chain that loses the REPOSITORY_SYSTEM_CALL marker. This causes MavenValidator to reject re-entrant calls that carry uninterpolated property expressions. This commit addresses the gap with two complementary fixes: 1. Session-scoped re-entrancy detection: Each public RepositorySystem method now maintains a depth counter in SessionData via enterSessionScope()/ exitSessionScope(). When the counter is > 0 on entry, the call is detected as re-entrant regardless of whether the RequestTrace chain carries the marker. This catches cases where consumers rebuild the trace chain from a different tracing system. 2. Skip managed dependency validation in validateCollectRequest(): Managed dependencies are declarative constraints that only take effect when a matching dependency is encountered during collection. Validating them eagerly rejects valid builds where a BOM imports managed dependencies with uninterpolated property expressions (e.g. ${osgi.version}) that are never actually used. If a managed dependency IS matched and its coordinates are invalid, the error surfaces during version/artifact resolution. Together, these changes allow Maven PR #12481 to remove the consumer-side workarounds for uninterpolated expressions in DefaultArtifactDescriptorReader, ArtifactDescriptorReaderDelegate, DefaultProjectDependenciesResolver, and DefaultDependencyResolver. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
See #2008 MavenValidator should just not implement new method. But in some other, non-Maven use cases, uninterpolated depMgt may be seen as error. |
Revert the managed dependency validation removal from DefaultRepositorySystemValidator. As cstamas noted, managed deps should remain validatable for non-Maven use cases — the proper separation between direct and managed dependency validation belongs in PR #2008 (validateManagedDependency method). This PR now focuses solely on session-scoped re-entrancy detection via an AtomicInteger depth counter in session data, which covers the broken trace chain scenario independently. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for the feedback @cstamas! You're right — removing managed dependency validation entirely is too broad. I've reverted the managed dependency validation changes in 7400592. This PR now focuses solely on session-scoped re-entrancy detection (the The managed dependency separation (direct vs managed validation) is better handled by #2008's |
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Extends re-entrancy detection in DefaultRepositorySystem to handle cases where consumers rebuild/break RequestTrace chains (e.g., Maven 4 trace conversion), and adjusts validation behavior to avoid rejecting builds due to uninterpolated expressions in managed dependencies.
Changes:
- Add session-scoped re-entrancy depth tracking (via
SessionData) to supplement trace-marker-based detection. - Wrap public
RepositorySystementry points with try/finally guards to ensure depth counters are decremented reliably. - Add new tests covering broken trace chains, managed dependency validation behavior, and depth cleanup.
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 session-scoped depth-based re-entrancy detection and applies it across public entry points. |
| maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRepositorySystemReentrancyTest.java | Adds regression tests for broken trace chains/uninterpolated expressions and validates depth counter cleanup. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private static boolean isReentrant(RequestTrace trace, RepositorySystemSession session) { | ||
| return isReentrant(trace) || getReentryDepth(session).get() > 0; | ||
| } | ||
|
|
||
| /** | ||
| * Increments the session-scoped re-entrancy depth counter. Must be called on every outermost | ||
| * entry into a public {@code RepositorySystem} method, and the returned {@link Runnable} must | ||
| * be invoked in a {@code finally} block to decrement the counter on exit. | ||
| * | ||
| * @param session the current repository system session | ||
| * @return a {@link Runnable} that decrements the depth counter when invoked | ||
| */ | ||
| private static Runnable enterSessionScope(RepositorySystemSession session) { | ||
| AtomicInteger depth = getReentryDepth(session); | ||
| depth.incrementAndGet(); | ||
| return depth::decrementAndGet; | ||
| } | ||
|
|
||
| private static AtomicInteger getReentryDepth(RepositorySystemSession session) { | ||
| return (AtomicInteger) session.getData().computeIfAbsent(SESSION_REENTRY_DEPTH_KEY, () -> new AtomicInteger(0)); | ||
| } |
| try { | ||
| systemRef.get().resolveVersionRange(s, innerRequest); | ||
| } catch (Exception e) { | ||
| fail("Re-entrant call with broken trace chain should succeed via session-scoped detection: " + e); | ||
| } |
gnodet
left a comment
There was a problem hiding this comment.
The session-scoped re-entrancy depth counter is a sound approach for catching broken trace chains. The try-finally guard pattern is consistently and correctly applied across all 8 public methods. A few observations:
Medium severity:
- 🧵 Session-scoped vs thread-scoped counter: The
AtomicIntegerdepth counter inSessionDatais shared across all threads using the same session. Re-entrancy is inherently per-call-stack, so in parallel builds with a shared session, Thread B's independent outermost call could seedepth > 0from Thread A and skip validation ("fail-open"). Practical impact is limited since the primary trace-basedisReentrant(trace)is per-call-stack and the session counter is a fallback for the Maven 4RequestTraceHelperedge case (single-threaded model builder). AThreadLocalwould be more semantically correct but may be over-engineering for this specific use case.
Low severity:
fail("msg: " + e)at test line 340 discards the exception stack trace. JUnit 5'sfail(String, Throwable)overload preserves the full context for easier diagnosis.- No test verifies that the depth counter is properly decremented when the delegate throws an exception. The try-finally pattern guarantees correctness, but a test proving cleanup on error would strengthen confidence.
Strengths:
- Clean revert of the managed dependency validation concern to the already-merged PR #2008.
- Consistent try-finally exitGuard across all 8 relevant methods; terminal operations (
install,deploy, etc.) correctly left unmodified. - The anonymous
Objectsubclass withtoString()for the session data key is a good pattern for identity-based key safety.
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
Summary
The trace-based re-entrancy detection added in PR #1957 does not fully cover all code paths. Specifically, Maven 4's
RequestTraceHelperconverts between Maven API traces and resolverRequestTraceobjects viatoResolver(), creating a fresh trace chain that loses theREPOSITORY_SYSTEM_CALLmarker. This causesMavenValidatorto reject the collect request in theMavenITgh12305integration test (PR apache/maven#12481) withInvalid Collect Request: null -> [].This PR addresses the gap with two complementary fixes:
Session-scoped re-entrancy detection (
DefaultRepositorySystem): Each public method now maintains a depth counter inSessionDataviaenterSessionScope()/try-finally decrement. When the counter is > 0 on entry, the call is detected as re-entrant regardless of whether theRequestTracechain carries the marker. This catches cases where consumers rebuild the trace chain from a different tracing system.Skip managed dependency validation (
DefaultRepositorySystemValidator): Managed dependencies are declarative constraints that only take effect when a matching dependency is encountered during collection. Validating them eagerly rejects valid builds where a BOM imports managed deps with uninterpolated expressions (e.g.${osgi.version}) that are never actually used. If a managed dependency IS matched and its coordinates are invalid, the error surfaces naturally during version/artifact resolution.Together these changes allow apache/maven#12481 to remove the consumer-side workarounds for uninterpolated expressions.
Context
Test plan
sessionScopedDetectionSkipsValidationWhenTraceChainIsBroken— verifies inner calls with broken trace + uninterpolated expressions succeed via session-scoped detectioncollectDependenciesAcceptsManagedDepsWithUninterpolatedExpressions— reproduces the MavenITgh12305 scenariocollectDependenciesStillRejectsInvalidDirectDependencies— ensures direct deps are still validatedsessionScopedDepthIsProperlyDecrementedOnExit— verifies counter cleanup for independent calls🤖 Generated with Claude Code