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

concurrent-api: cleanup AsyncContext operations #3181

Conversation

bryce-anderson
Copy link
Contributor

@bryce-anderson bryce-anderson commented Jan 30, 2025

Motivation:

Perhaps the main goal of the AsyncContext is to allow a way to bundle up thread-local (ish) state and restore it when an async operation occurs. However, restore operation is essentially inlined in all the context operations which makes the mechanism more difficult to understand than it needs to be.

Modifications:

Add a few new methods to the AsyncContextProvider

  • captureContext() the goal of this method is to package up context for propagation to the next continuation. This stands in contrast to the existing context() method which is used for reading the async ContextMap.
  • attachContext(ContextMap) this method is used to restore the local state for temporary use. It returns a new Scope type, which then reverts this local environment to what it was before the restore.

Result:

Cleaner async context operations.

Motivation:

Perhaps the main goal of the AsyncContext is to allow a way to bundle up
thread-local (ish) state and restore it when an async operation occurs.
However, restore operation is essentially inlined in all the context
operations which makes the mechanism more difficult to understand than
it needs to be.

Modifications:

Add a few new methods to the AsyncContextProvider

- saveContext() the goal of this method is to package up context for
  propagation to the next continuation. This stands in contrast to the
  existing context() method which is used for reading the async ContextMap.
- attachContext(ContextMap) this method is used to restore the local state
  for temporary use. It returns a new Scope type, which then reverts
  this local environment to what it was before the restore.

Result:

Cleaner async context operations.
* wrapped to bundle up additional contexts such as the OpenTelemetry or grpc contexts.
* @return the saved context state that may be restored later.
*/
ContextMap saveContext();
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 an ideal world we would make the fact that this is a ContextMap abstract and have an interface that looks something like this

interface SavedContext {
  Scope restoreContext();
}

However, our own uses are relatively complex and furthermore people are already writing agent code that make this change difficult. See the DataDog java-agent implementation here which depends on the saved context state being a ContextMap.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See bryce-anderson@5806c78 for a commit that unwinds current usage of the ContextMap directly. Note that there would still be a bit more to do.

Copy link
Member

Choose a reason for hiding this comment

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

I'm fine with the current name, just want to propose other options for consideration: wdyt about naming it grabContext() or captureContext()? Will those sound more like we are taking a snapshot in preparation for a jump?

Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

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

Massive boilerplate reduction 🔪

* wrapped to bundle up additional contexts such as the OpenTelemetry or grpc contexts.
* @return the saved context state that may be restored later.
*/
ContextMap saveContext();
Copy link
Member

Choose a reason for hiding this comment

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

I'm fine with the current name, just want to propose other options for consideration: wdyt about naming it grabContext() or captureContext()? Will those sound more like we are taking a snapshot in preparation for a jump?


import javax.annotation.Nullable;

import static io.servicetalk.concurrent.api.AsyncContextMapThreadLocal.CONTEXT_THREAD_LOCAL;
import static java.util.Objects.requireNonNull;

class ContextPreservingSingleSubscriber<T> implements Subscriber<T> {
Copy link
Member

Choose a reason for hiding this comment

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

Not necessary for now, but whenever you have time could you please verify that we indeed call AsyncContextProvider.wrapSingleSubscriber(...) and similar wrappers for Completable/Publisher in all operators whey it's necessary. Would be great to make sure we don't miss context propagation.

Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

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

🚀

Copy link
Contributor

@mgodave mgodave left a comment

Choose a reason for hiding this comment

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

LOVE seeing all the code being deleted!

@bryce-anderson bryce-anderson merged commit c6d55e2 into apple:main Feb 4, 2025
11 checks passed
@bryce-anderson bryce-anderson deleted the bl_anderson/AsyncContextProviderCleanup branch February 4, 2025 22:02
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