Skip to content

Extend re-entrancy detection for broken trace chains#2009

Open
gnodet wants to merge 3 commits into
masterfrom
the-re-entrancy-detection-added-in-pr-1957-merge
Open

Extend re-entrancy detection for broken trace chains#2009
gnodet wants to merge 3 commits into
masterfrom
the-re-entrancy-detection-added-in-pr-1957-merge

Conversation

@gnodet

@gnodet gnodet commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

The trace-based re-entrancy detection added in PR #1957 does not fully cover all code paths. Specifically, Maven 4's RequestTraceHelper converts between Maven API traces and resolver RequestTrace objects via toResolver(), creating a fresh trace chain that loses the REPOSITORY_SYSTEM_CALL marker. This causes MavenValidator to reject the collect request in the MavenITgh12305 integration test (PR apache/maven#12481) with Invalid 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 in SessionData via enterSessionScope()/try-finally decrement. 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.

  • 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

  • New test: sessionScopedDetectionSkipsValidationWhenTraceChainIsBroken — verifies inner calls with broken trace + uninterpolated expressions succeed via session-scoped detection
  • New test: collectDependenciesAcceptsManagedDepsWithUninterpolatedExpressions — reproduces the MavenITgh12305 scenario
  • New test: collectDependenciesStillRejectsInvalidDirectDependencies — ensures direct deps are still validated
  • New test: sessionScopedDepthIsProperlyDecrementedOnExit — verifies counter cleanup for independent calls
  • CI: all existing + new tests pass

🤖 Generated with Claude Code

gnodet and others added 2 commits July 22, 2026 15:28
…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>
@cstamas

cstamas commented Jul 22, 2026

Copy link
Copy Markdown
Member

See #2008

MavenValidator should just not implement new method. But in some other, non-Maven use cases, uninterpolated depMgt may be seen as error.

@gnodet
gnodet marked this pull request as ready for review July 23, 2026 11:43
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>
@gnodet

gnodet commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

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 AtomicInteger depth counter in session data), which addresses the broken trace chain scenario independently of the managed dependency question.

The managed dependency separation (direct vs managed validation) is better handled by #2008's validateManagedDependency() approach, which lets MavenValidator abstain while keeping the validation available for non-Maven consumers.

@gnodet gnodet changed the title Extend re-entrancy detection for broken trace chains and managed dep validation Extend re-entrancy detection for broken trace chains Jul 23, 2026
@elharo
elharo requested a review from Copilot July 24, 2026 12:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 RepositorySystem entry 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.

Comment on lines +686 to +706
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));
}
Comment on lines +336 to +340
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 gnodet left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 AtomicInteger depth counter in SessionData is 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 see depth > 0 from Thread A and skip validation ("fail-open"). Practical impact is limited since the primary trace-based isReentrant(trace) is per-call-stack and the session counter is a fallback for the Maven 4 RequestTraceHelper edge case (single-threaded model builder). A ThreadLocal would 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's fail(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 Object subclass with toString() 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants