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

Support across thread tracing for SOFA-RPC #675

Merged
merged 10 commits into from
Mar 25, 2024

Conversation

OrezzerO
Copy link
Contributor

@OrezzerO OrezzerO commented Mar 20, 2024

Now SOFA-PRC plugin only support in thread tracing. It does not support across thread tracing. InvokeCallback class is the key class which does the async callback work. This pr add the ability of across thread tracing by enhancing InvokeCallback class the way like org.apache.skywalking.apm.toolkit.activation.trace.CallableOrRunnableActivation do.

  • Tests(including UT, IT, E2E) are added to verify the new feature.

  • If this pull request closes/resolves/fixes an existing issue, replace the issue number. Closes #.

  • Update the CHANGES log.

@wu-sheng
Copy link
Member

Please explain your scenario. When this happens, and what was not working.

@wu-sheng
Copy link
Member

I think you should add callback in https://github.com/apache/skywalking-java/tree/main/test/plugin/scenarios/sofarpc-scenario and verify the callback spans through the expected file.

@wu-sheng
Copy link
Member

@OrezzerO Is #676 somehow affected this plugin? Is SOFA requiring JDK21? It seems not. Could you change your local JDK to JDK8 or 11 to finish this?

@OrezzerO
Copy link
Contributor Author

@OrezzerO Is #676 somehow affected this plugin? Is SOFA requiring JDK21? It seems not. Could you change your local JDK to JDK8 or 11 to finish this?

I can change my jdk. I am not familiar with skywalking scenario test, need some time to finish this work.

@wu-sheng
Copy link
Member

I think it is not hard to run. The basic workflow is, the test is using a demo app with a compiled latest Java agent up and running. Then there is a mock traffic generator to trigger traces to the mock collector. The last step is to write expected spans to verify.

@OrezzerO
Copy link
Contributor Author

I have commit some code to verify the callback spans through the expected file. However I have some thing not very sure:

  1. Is expectedData.yaml file strongly depends on the stop order of each span? I am using async invoke ,so the stop order is not fixed. It may affect CI success rate.
  2. I am not sure if "my expect of cross thread tracing" is suitable for skywalking. Please review test/plugin/scenarios/sofarpc-scenario/config/expectedData.yaml carefully.

@@ -17,6 +17,23 @@ segmentItems:
- serviceName: sofarpc-scenario
segmentSize: gt 2
segments:
- segmentId: not null
spans:
- operationName: HEAD:/sofarpc-scenario/case/healthCheck
Copy link
Member

Choose a reason for hiding this comment

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

/healthCheck should not be included in the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed it.

@@ -34,6 +51,37 @@ segmentItems:
refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not
null, parentService: sofarpc-scenario, traceId: not null}
skipAnalysis: 'false'
- segmentId: not null
Copy link
Member

Choose a reason for hiding this comment

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

Is expectedData.yaml file strongly depends on the stop order of each span? I am using async invoke ,so the stop order is not fixed. It may affect CI success rate.

Considering this is a new segment, which part of the span order are you referring to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

image

I mean main thread segment and callback thread segment. Which segment will stop first is not fixed.

I try to sleep in main thread to make main thread segment stop after callback thread segment. The test runs successfully. So, segment order in expectedData.yaml is ignored?

Copy link
Member

Choose a reason for hiding this comment

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

Segment order is not a part of validation in the file. You don't need to worry about it.

Comment on lines 64 to 68
spanType: Exit
peer: 127.0.0.1:12200
skipAnalysis: false
tags:
- {key: url, value: 'bolt://127.0.0.1:12200/org.apache.skywalking.apm.testcase.sofarpc.interfaces.SofaRpcDemoService.onAppResponse(java.lang.String)'}
Copy link
Member

Choose a reason for hiding this comment

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

By reading this, the callback is calling another RPC?

Copy link
Member

Choose a reason for hiding this comment

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

I think we could make the test a little simpler. Just end the chain from Thread/com.alipay.sofa.rpc.message.bolt.BoltInvokerCallback/onResponse. We don't have to do another RPC. So this one, and the other new entry span(new segment) is not necessary, too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes callback is calling another RPC. I will simplify it.

@wu-sheng wu-sheng added this to the 9.2.0 milestone Mar 21, 2024
@Override
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
if (ContextManager.isActive()) {
objInst.setSkyWalkingDynamicField(ContextManager.capture());
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 risk of using this field to propagate tracing context. The input(inherit InvokeCallback) is a object, which could be reused by many callback invocations.
If SOFA RPC indicates this callback service should never be shared with different RPCs? Especially if this callback could be statusless.

An alternative solution is, you could use a wrapper, a plugin level InvokeCallback implementation to wrap the original one. We could make sure, that is one time used, and add a field(not need to be dynamic field) to hold the context.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the context of SOFARPC, InvokeCallback is a wrapper. Users directly use the SofaResponseCallback class. Each SOFARPC callback request corresponds uniquely to an InvokeCallback.

However, in the context of SOFA BOLT (BOLT being a wrapper for Netty which SOFARPC relies on), InvokeCallback may be reused.

It's a good idea to create a plugin level wrapper, but I am afraid I will introduce an error because of cannot dealing with classloader correctly.

I plan to create a class InvokeCallbackWrapper in plugin code. How to make it loaded by business classloader?

public class InvokeCallbackWrapper implements InvokeCallback {

    private ContextSnapshot contextSnapshot;
    private InvokeCallback invokeCallback;

    public InvokeCallbackWrapper(InvokeCallback invokeCallback) {
        this.contextSnapshot = ContextManager.capture();
        this.invokeCallback = invokeCallback;

    }

    @Override
    public void onResponse(final Object o) {
        ContextManager.createLocalSpan("Thread/" + invokeCallback.getClass().getName() + "/onResponse");
        if (contextSnapshot != null) {
            ContextManager.continued(contextSnapshot);
        }
        try {
            invokeCallback.onResponse(o);
        } catch (Throwable t) {
            ContextManager.activeSpan().log(t);
            throw t;
        } finally {
            ContextManager.stopSpan();
            contextSnapshot = null;
        }

    }

    @Override
    public void onException(final Throwable throwable) {
        ContextManager.createLocalSpan("Thread/" + invokeCallback.getClass().getName() + "/onException");
        if (contextSnapshot != null) {
            ContextManager.continued(contextSnapshot);
        }
        if (throwable != null) {
            AbstractSpan abstractSpan = ContextManager.activeSpan();
            if (abstractSpan != null) {
                abstractSpan.log(throwable);
            }
        }
        try {
            invokeCallback.onException(throwable);
        } catch (Throwable t) {
            ContextManager.activeSpan().log(t);
            throw t;
        } finally {
            ContextManager.stopSpan();
            contextSnapshot = null;
        }
    }

    @Override
    public Executor getExecutor() {
        return invokeCallback.getExecutor();
    }
}

Copy link
Member

Choose a reason for hiding this comment

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

I plan to create a class InvokeCallbackWrapper in plugin code. How to make it loaded by business classloader?

All plugin interceptor relative codes are loaded in your target class loader, otherwise, all existing logic will fail. I think in this case, users of SOFA would not be aware of the wrapper, as it is only running internally.

Copy link
Member

Choose a reason for hiding this comment

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

Classloader doesn't need to be worried, AFAIK.

Comment on lines 86 to 92
protected ContextSnapshot getContextSnapshot() {
return contextSnapshot;
}

protected InvokeCallback getInvokeCallback() {
return invokeCallback;
}
Copy link
Member

Choose a reason for hiding this comment

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

These could be replaced by annotations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

@wu-sheng
Copy link
Member

Generally, this PR is good, just some nits. Please fix them, then it is good to merge.

@wu-sheng
Copy link
Member

I tried to apply the change, but it seems not work.

@OrezzerO
Copy link
Contributor Author

I tried to apply the change, but it seems not work.

Missed import lombok.AccessLevel; statement; added it.

@wu-sheng wu-sheng merged commit a751e32 into apache:main Mar 25, 2024
188 checks passed
@OrezzerO OrezzerO deleted the zcx/sofa-rpc-callback branch March 25, 2024 03:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants