Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix unwrap methods to return cast delegates rather than cast 'this' #8298

Merged
merged 6 commits into from
Jan 30, 2024

Conversation

tjquinno
Copy link
Member

Description

Resolves #8200

This PR basically does these things:

  1. Remove the default interface implementation of unwrap from Tracer, Span, and Span.Builder. The code would cast this but the classes were package-private and inaccessible to developers.
  2. Implement proper logic to cast and return the delegate rather than this in the various implementations.
  3. Slightly revise the asParent implementations which used unwrap and now just simply cast.
  4. Add tests.

Documentation

Bug fix; no doc impact

@tjquinno tjquinno requested a review from tomas-langer January 25, 2024 16:38
@tjquinno tjquinno self-assigned this Jan 25, 2024
@oracle-contributor-agreement oracle-contributor-agreement bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Jan 25, 2024
@@ -39,8 +39,7 @@ public String spanId() {

@Override
public void asParent(io.helidon.tracing.Span.Builder<?> spanBuilder) {
spanBuilder.unwrap(OpenTelemetrySpanBuilder.class)
.parent(context);
((OpenTelemetrySpanBuilder) spanBuilder).parent(context);
Copy link
Member

Choose a reason for hiding this comment

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

There is a reason it is calling unwrap - the implementation that we receive may behave differently from what you expect, and provide the correct type, or maybe throw a more relevant exception than just ClassCastException

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess I do not understand what you mean.

The unwrap method has always been declared as public so, presumably, intended to be of use to app developers.

Prior to this PR the default implementation of unwrap defined at Span.Builder(Class<T> type) returned type.cast(this) which is OK for Helidon code in the same package because this is always a package-private Helidon type such as OpenTelemetrySpanBuilder or OpenTracingSpanBuilder. But returning this from there is not useful for app developers because they cannot access those package-private Helidon classes.

So this PR, in line with other unwrap implementations (such as in Tracer), changes unwrap so it now returns the delegate instead of this (cast of course to the requested type). The delegate type is accessible to app developers--because the delegate is a public OpenTracing or OpenTelemetry type accessible to Helidon code-- and therefore of use to an app developer.

Because that revision changes the behavior of unwrap, these prior internal uses of unwrap which you pointed to would need to change. But the net effect of the changed code you linked to is exactly the same as before: the old use of the old unwrap method would cast the span builder's this to the specified type, and that's precisely what the revised code in the PR does at those places except the revised asParent code just does the casting itself.

Copy link
Member Author

Choose a reason for hiding this comment

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

(after private DM exchange)

I have revised the PR so the implementations of unwrap do their own type checking and exception throwing. I think that's the revision you were asking for, although I am not sure I agree it's really needed or desirable.

(Sorry for the long message which follows. I think we are very close to agreeing on this but do differ on one point.)

I do not see what assumption you think the PR code is making about the types. Both in main and in the PR the caller can pass any type to unwrap. If the type is compatible, an object is cast to the type and returned; otherwise the code throws an exception.

And in both cases ultimately the code in Class.cast runs which does its own type check and exception throwing. The PR code simply delegates that work to Class.cast rather than duplicating the type check that Class.cast will do anyway and throwing our own IllegalArgumentException if the type is incompatible. Maybe that is what you are seeing as an assumption, but in the original PR code the unwrap implementations did not assume anything; that code simply attempts to do what the caller asks.

Your comment on the PR suggested "maybe throw a more relevant exception than just ClassCastException. TBH I do not see why IllegalArgumentException would be more relevant; in fact it's more general than ClassCastException--the caller is truly asking to have an object cast to an incompatible class. For the IAE to be clear, its message must describe the class mismatch as the underlying reason anyway.

Even so, now in the updated PR our unwrap methods do their own type checking and throw their own exceptions.

All that aside, the main problem was that the Span and Span.Builder interfaces provide default implementations of unwrap which checks and casts this but only OpenTracingSpan provided its own overriding implementation which checks and casts the delegate. The following classes have delegate instances inside them and should have--but did not--override the default implementation of unwrap to work with the delegate:

  • OpenTracingSpanBuilder
  • OpenTelemetrySpan
  • OpenTelemetrySpanBuilder

The main point of the PR is to update those classes so they override the default implementation of unwrap so they use their delegates.

@@ -38,8 +38,7 @@ public String spanId() {

@Override
public void asParent(Span.Builder<?> spanBuilder) {
spanBuilder.unwrap(OpenTracingSpanBuilder.class)
.parent(this);
((OpenTracingSpanBuilder) spanBuilder).parent(this);
Copy link
Member

Choose a reason for hiding this comment

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

Same here - unwrap I think is reasonable here

@tjquinno tjquinno merged commit 52a5a1b into helidon-io:main Jan 30, 2024
12 checks passed
@tjquinno tjquinno deleted the 4.x-tracing-unwrap branch January 30, 2024 18:51
hrstoyanov pushed a commit to hrstoyanov/helidon that referenced this pull request Feb 23, 2024
…elidon-io#8298)

* Fix unwrap methods to return cast delegates rather than cast 'this'

Signed-off-by: Tim Quinn <tim.quinn@oracle.com>

* Use a JUnit extension to set-up and shutdown OTel for tests

* Review comments: add explicit type checking and exception throwing in unwrap implementations instead of simply delegating to Class.cast

* Enhance unwrap to handle package-local and public implementations

* Enhance unwrap for spans and no-op tracer also

* Restore default implementations of unwrap

---------

Signed-off-by: Tim Quinn <tim.quinn@oracle.com>
tvallin pushed a commit to tvallin/helidon that referenced this pull request Feb 28, 2024
…elidon-io#8298)

* Fix unwrap methods to return cast delegates rather than cast 'this'

Signed-off-by: Tim Quinn <tim.quinn@oracle.com>

* Use a JUnit extension to set-up and shutdown OTel for tests

* Review comments: add explicit type checking and exception throwing in unwrap implementations instead of simply delegating to Class.cast

* Enhance unwrap to handle package-local and public implementations

* Enhance unwrap for spans and no-op tracer also

* Restore default implementations of unwrap

---------

Signed-off-by: Tim Quinn <tim.quinn@oracle.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OCA Verified All contributors have signed the Oracle Contributor Agreement.
Projects
None yet
2 participants