Skip to content

Keep spans active during AsyncHandler methods in AsyncHttpClient#1172

Merged
felixbarny merged 10 commits intoelastic:masterfrom
milanvdm:instrument-on-complete-async-http
May 8, 2020
Merged

Keep spans active during AsyncHandler methods in AsyncHttpClient#1172
felixbarny merged 10 commits intoelastic:masterfrom
milanvdm:instrument-on-complete-async-http

Conversation

@milanvdm
Copy link
Contributor

@milanvdm milanvdm commented May 4, 2020

What does this PR do?

Currently, when using a custom AsyncHandler with the AsyncHttpClient, there is no active span in the callback methods.
This PR makes sure that span are activated when entering those methods making it possible for users to use tracing-context in those methods.

Checklist

  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated CHANGELOG.asciidoc
  • I have updated supported-technologies.asciidoc
  • Added an API method or config option? Document in which version this will be introduced
  • Added an instrumentation plugin? How did you make sure that old, non-supported versions are not instrumented by accident?

public static AsyncHandler<Response> customAsyncHandler = new AsyncCompletionHandler<Response>() {
@Override
public State onStatusReceived(HttpResponseStatus responseStatus) {
assert(tracer.currentTransaction() != null);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure how to add a proper test making sure the context in the methods are active or not.

@milanvdm milanvdm mentioned this pull request May 4, 2020
9 tasks
@ghost
Copy link

ghost commented May 4, 2020

💚 Build Succeeded

Pipeline View Test View Changes Artifacts preview stats

Expand to view the summary

Build stats

Test stats 🧪

Test Results
Failed 0
Passed 1253
Skipped 12
Total 1265

@@ -179,6 +179,14 @@ public AsyncHandlerOnCompletedInstrumentation() {

@Advice.OnMethodEnter(suppress = Throwable.class)
private static void onMethodEnter(@Advice.This AsyncHandler<?> asyncHandler) {
Copy link
Member

Choose a reason for hiding this comment

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

Making sure that wrapped AsyncHandlers work as expected (activate and deactivate only in outer-most AsyncHandler

Suggested change
private static void onMethodEnter(@Advice.This AsyncHandler<?> asyncHandler) {
private static void onMethodEnter(@Advice.This AsyncHandler<?> asyncHandler, @Advice.Local("span") Span span) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Bit unsure on what you mean with activate and deactive in the outer-most AsyncHandler?

Copy link
Member

Choose a reason for hiding this comment

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

Following the decorator pattern, calling AsyncHandler#onComplete may result in the handler calling another, wrapped/nested AsyncHandler#onComplete. We want to make sure that only the first AsyncHandler activates and deactivates the span.

In your first draft, the first AsyncHandler would activate the span but the last AsyncHandler in the chain would deactivate it. This would result in the span not being active in the first AsyncHandler after it called onComplete on its delegate handler.

    private final AsyncHandler<Response> delegate;

    @Override
    public Response onCompleted(Response response) {
        try {
            return delegate.onCompleted(response);
        } finally {
             tracer.getActive() // returns null
        }
    }

By propagating the active span via a @Advice.Local, we have fixed that problem.


@Override
public Response onCompleted(Response response) {
assertThat(tracer.currentTransaction()).isNotNull();
Copy link
Member

Choose a reason for hiding this comment

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

To double-check, do the tests fail when doing isNull()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ill double-check all PR requirements now and make sure the tests make sense :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mmm, the changes make the existing tests fail (even when I remove the newly added customAsyncHandler.

testHttpCall[3]  Time elapsed: 0.519 sec  <<< ERROR!
org.awaitility.core.ConditionTimeoutException:
Assertion condition defined as a lambda expression in co.elastic.apm.agent.MockReporter
Expecting actual not to be empty within 500 milliseconds.

Seems to me that is a local problem linked to my laptop. Will try to figure out why it is doing that. Any chance you have a docker-image able to run the tests?

Copy link
Member

Choose a reason for hiding this comment

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

The tests are failing on CI for the same reason. Not sure what causes that 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh wow, interesting. Very strange that the new instrumentations make the current tests time-out. Will have a look later today when I have a bit more time :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to figure it out but tbh, I have no idea.
Only thing I could think of was that Im using a new AsyncCompletionHandler<Response> in the test and somehow the methods of this subclass don't get matched properly.

Copy link
Contributor Author

@milanvdm milanvdm May 7, 2020

Choose a reason for hiding this comment

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

@felixbarny I've played around with it a bit more:

@Advice.OnMethodEnter(suppress = Throwable.class)
        private static void onMethodEnter(@Advice.This AsyncHandler<?> asyncHandler, @Advice.Local("span") Span span) {
            span = handlerSpanMap.remove(asyncHandler);
            if (span != null) {
                span.activate();
                System.out.println(tracer.currentTransaction());
                System.out.println("=======================");
            }
        }

This prints out:

null
=======================

So it seems that activating the span does not mean I can do assertThat(tracer.currentTransaction()).isNotNull(); in the test.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, that's true. You should assert that tracer.getActive() is not null instead. I agree that if tracer.getActive() returns non-null, tracer.getTranaction() should do, too. I have an idea how to accomplish that. In the meantime, please assert on tracer.getActive().

Copy link
Member

Choose a reason for hiding this comment

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

See also #1174

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool, thanks :)
Ive applied the changes and the tests now pass correctly!

@codecov-io
Copy link

codecov-io commented May 5, 2020

Codecov Report

Merging #1172 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff            @@
##             master    #1172   +/-   ##
=========================================
  Coverage     59.67%   59.67%           
  Complexity       83       83           
=========================================
  Files           328      328           
  Lines         15026    15026           
  Branches       2091     2091           
=========================================
  Hits           8967     8967           
  Misses         5448     5448           
  Partials        611      611           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 967b0c6...4cf8353. Read the comment docs.

@felixbarny felixbarny merged commit fddb645 into elastic:master May 8, 2020
@felixbarny
Copy link
Member

Great work, thanks! ❤️

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