Skip to content

pekko: Improve PekkoRouteHolder#16390

Merged
trask merged 15 commits into
open-telemetry:mainfrom
samwright:fix-pekko-routeholder-immutable3
May 5, 2026
Merged

pekko: Improve PekkoRouteHolder#16390
trask merged 15 commits into
open-telemetry:mainfrom
samwright:fix-pekko-routeholder-immutable3

Conversation

@samwright

Copy link
Copy Markdown
Contributor
  • Give each Directive its own PekkoRouteHolder to allow concurrent access without causing problems like those described in NullPointerException and ArrayIndexOutOfBoundsException with pekko-http #15681. Only when there's a route match are the PekkoRouteHolders in that call stack combined.
  • Remove need for state queue
  • remove query params from tapir http.route, as required by otel convention
  • support Tapir's internal route concatenation feature

- Give each Directive its own PekkoRouteHolder to avoid concurrent access. Only when there's a route match are the PekkoRouteHolders in that call stack combined.
- Remove need for state queue
- remove query params from tapir http.route, as required by otel convention
- support Tapir's internal route concatenation feature
@samwright
samwright requested a review from a team as a code owner March 5, 2026 09:28

This comment was marked as outdated.

@trask trask mentioned this pull request Apr 17, 2026
samwright and others added 2 commits April 18, 2026 14:19
- Fix Nullable import
- Add volatile to fields accessed in different threads
- Move constructor to top of PekkoRouteHolder
- Use '.flatMap(routeHolder -> Optional.ofNullable(routeHolder.route()))' to make clear that `routeHolder.route()` can return `null`.
@samwright
samwright requested a review from trask April 18, 2026 14:09
@trask

trask commented Apr 19, 2026

Copy link
Copy Markdown
Member

hey @samwright, fyi both of the above reviews from me were really from AI (based on proposal in #17889), sorry I thought I had mentioned that above, but apparently not

- LinkedList -> ArrayList + ArrayDeque
- no unnecessary onThrowable in advice
@samwright

Copy link
Copy Markdown
Contributor Author

hey @samwright, fyi both of the above reviews from me were really from AI (based on proposal in #17889), sorry I thought I had mentioned that above, but apparently not

That's cool - I had already guessed :-)

@samwright
samwright requested a review from trask April 20, 2026 05:39
Comment on lines +105 to +110
PekkoRouteHolder routeHolder = PekkoRouteHolder.create();
Context context =
instrumenter()
.start(parentContext, request)
.with(routeHolder)
.with(new PekkoFallbackRouteHolder());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

does it make sense to store the PekkoFallbackRouteHolder inside the PekkoRouteHolder, and not add the PekkoFallbackRouteHolder to the context?

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 PekkoRouteHolder is attached to the HttpRequest, and each time PekkoRouteWrapper is applied another PekkoRouteHolder instance is created and attached to the HttpRequest. So adding anything to one of the child routeHolders won't affect the original routeHolder. We could find the original by calling routeHolder.parent until there is no parent. Would that be preferable to having this extra Context item?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

that sounds good, it's nice to avoid extra context items, as long as the alternative isn't complex

public void setOverride(PekkoRouteHolder override) {
if (override.override != null) {
// The given override itself has an override, so just use that one instead
this.override = override.override;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can there be multiple layers of overrides?

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.

No, this branch ensures that if the given routeHolder A has an override B, this.override is set to B. Since every holder starts with no override, this ensures the max depth of overide.overide.overide... is 1.

public String route() {
return lastWasMatched ? route.toString() : null;
public boolean hasRoute() {
return override != null || lastWasMatched;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

similar question whether overrides can have overrides (and if that affects calculation here)?

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.

see answer below, but no it's not possible.

trask added a commit to trask/opentelemetry-java-instrumentation that referenced this pull request Apr 30, 2026
The previous prompt's guidelines were under-specified, so the model ran on intuition: when an approver had left review questions and the author hadn't replied (open-telemetry#16390), it picked 'approver' instead of 'author'.

Restate the rules as an ordered first-match decision tree (external > author-owes-response > approver-default), with explicit edge cases: an approver *commit* does not put the ball in the author's court, and an approver comment linking to an upstream issue should bucket as external.
trask and others added 2 commits May 2, 2026 22:18
…er-immutable3

# Conflicts:
#	instrumentation/pekko/pekko-http-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/pekkohttp/v1_0/server/tapir/TapirPathInstrumentation.java
- Don't precalculate string length
- use `_` for ignored variables in test
@samwright
samwright requested a review from trask May 4, 2026 12:27
- Remove PekkoFallbackRouteHolder

@trask trask left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! One optional suggestion if it makes sense to you.

Comment on lines +89 to 104
boolean shouldAddFinalWildcard = lastUnmatchedPath != null && !lastUnmatchedPath.isEmpty();
Deque<String> routePaths = new ArrayDeque<>();

for (PekkoRouteHolder routeHolder = this;
routeHolder != null;
routeHolder = routeHolder.parent) {

for (int i = routeHolder.paths.size() - 1; i >= 0; i--) {
routePaths.addFirst(routeHolder.paths.get(i));
}
}
if (shouldAddFinalWildcard) {
routePaths.addLast("*");
}
return String.join("", routePaths);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional: a parent-recursion appendRoute helper is a bit shorter and avoids the intermediate Deque plus the reverse-iteration over each holder's paths.

Suggested change
boolean shouldAddFinalWildcard = lastUnmatchedPath != null && !lastUnmatchedPath.isEmpty();
Deque<String> routePaths = new ArrayDeque<>();
for (PekkoRouteHolder routeHolder = this;
routeHolder != null;
routeHolder = routeHolder.parent) {
for (int i = routeHolder.paths.size() - 1; i >= 0; i--) {
routePaths.addFirst(routeHolder.paths.get(i));
}
}
if (shouldAddFinalWildcard) {
routePaths.addLast("*");
}
return String.join("", routePaths);
}
StringBuilder sb = new StringBuilder();
appendRoute(sb);
if (lastUnmatchedPath != null && !lastUnmatchedPath.isEmpty()) {
sb.append('*');
}
return sb.toString();
}
private void appendRoute(StringBuilder sb) {
if (parent != null) {
parent.appendRoute(sb);
}
for (String path : paths) {
sb.append(path);
}
}

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.

I like the brevity, but that Deque is being replaced by frames in the stack, which could be a problem for very long/complex PathMatchers.
The reverse-iteration is cheap (operating on an ArrayList) so I don't think there's a performance improvement here (happy to be proven wrong though). I could replace Strings.join with a StringBuilder, if that's preferred?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

makes sense, let's merge as-is, thanks!

@github-actions github-actions Bot mentioned this pull request May 5, 2026
@trask
trask merged commit dfef60e into open-telemetry:main May 5, 2026
92 checks passed
@otelbot

otelbot Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Thank you for your contribution @samwright! 🎉 We would like to hear from you about your experience contributing to OpenTelemetry by taking a few minutes to fill out this survey.

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