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

[#1962] Add newCallbackExecutor to interceptors and override for OpenTracing #2366

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ghaskins
Copy link

This change adds newCallbackExecutor to the OutboundInterceptor interface, overriding the OpenTracing behavior. This allows the OpenTracing interceptor to transfer any active trace/span from the point of creation to the point of deferred execution, thus preserving the span within the callback.

Without this, spans become disjoint across Promise.thenCompose, due to the nature of the deferring the execution of the .thenCompose to a callback thread.

Fixes #1962

Checklist

  1. Closes Tracing context does not propagate into .thenCompose #1962

  2. How was this tested:
    A unit test was developed first to confirm the issue. The solution was iterated over until the unit test passed satisfactorily. The unit-test is included in the MR for inclusion with the code-base

  3. Any docs updates needed?
    No.

@ghaskins ghaskins requested a review from a team as a code owner January 13, 2025 18:52
@cretz
Copy link
Member

cretz commented Jan 13, 2025

Hrmm, this is a very specific interceptor-oriented solution to a general problem of contextual state across promises. I wonder if a WorkflowAsyncLocal is best which behaves like thread local, but also propagates across promises. This is the common solution to this problem in other languages, make a way to propagate state across async (Go is context, Python is contextvars, .NET is AsyncLocal, etc).

@ghaskins
Copy link
Author

@cretz, if I understand what you are saying, I think we'd still need to use interception at some level unless OTEL can be made to utilize the WorkflowAsyncLocal instead of ThreadLocal because something would need to initialize the state in the callback context, which currently runs outside of the RootThread complex. I'm not saying OTEL can't use WorkflowAsyncLocal; I just don't know

for (Functions.Proc handler : handlers) {
handler.apply();
}
executor.execute(() -> handlers.forEach(Functions.Proc::apply));
Copy link
Member

Choose a reason for hiding this comment

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

I believe this change is not backward compatible and can break determinism.

Copy link
Author

Choose a reason for hiding this comment

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

@mfateev are you referring to the forEach or the executor deferment?

Copy link
Author

@ghaskins ghaskins Jan 13, 2025

Choose a reason for hiding this comment

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

I should add: Executor makes no assumptions about async/sync execution models...I chose it because it seemed to have the right abstraction where the context creation is decoupled from the execution of the Runnable. However, I fully expect any interceptor only to use synchronous execution to avoid changing the model. I could make this more explicit or use a different abstraction if we think the use of Executor confusingly suggests async or promotes abuse.

Copy link
Author

Choose a reason for hiding this comment

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

I added documentation to clarify the synchronous requirement and changed to a plain for() loop.

Copy link
Member

Choose a reason for hiding this comment

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

My point is that switching from direct call to an executor that might change the order of execution of these calls can lead to non determinism. Such changes should be protected by versioning/patching.

Copy link
Author

Choose a reason for hiding this comment

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

Ah, I see. FWIW: I was careful to design the executor chain in a way that should respect the original order of interceptors, and I retained the original order of the for loop as well. I suspect your concern would be if someone added an interceptor that creates an NDE situation, but I think that if that were the case, they should follow patching procedures in the intercept code. I don't think we could solve that at this level. Thoughts?

Copy link
Author

Choose a reason for hiding this comment

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

@mfateev I looked, and I don't see any apparent examples of SDK code using patching internally. Since I don't believe the PR code changes the ordering, the only NDE potential that I see would be if an interceptor introduced a determinism issue in its handler. However, that would also concern any existing interception points, so it's unclear if this proposed one should be treated differently.

This is a blocking issue for me, so I am highly motivated to see it get merged. So please let me know what you'd like to see done here, and I will do my best to make it happen.

@ghaskins ghaskins force-pushed the issue-1962 branch 2 times, most recently from a572f48 to e714fc3 Compare January 13, 2025 21:03
…e for OpenTracing

This change adds newCallbackExecutor to the OutboundInterceptor interface,
overriding the OpenTracing behavior. This allows the OpenTracing interceptor
to transfer any active trace/span from the point of creation to the point of
deferred execution, thus preserving the span within the callback.

Without this, spans become disjoint across Promise.thenCompose, due to the
nature of the deferring the execution of the .thenCompose to a callback thread.

Fixes temporalio#1962

Signed-off-by: Greg Haskins <[email protected]>
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.

Tracing context does not propagate into .thenCompose
4 participants