Skip to content

fix: respect custom coroutine scope when calling blocking functions in a data fetcher #2116

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

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

Conversation

maxkomarychev
Copy link

@maxkomarychev maxkomarychev commented May 27, 2025

📝 Description

When running blocking function data fetcher it does not respect coroutine scope provided in the GraphQL context.

In my project we're creating custom a coroutine scope in order to propagate MDC and OTEL contexts in a coroutine like this:

CoroutineScope(MDCContext() + Dispatchers.IO + Context.current().asContextElement())

(here Context is an instance of OTEL context).

This works well in suspended functions since they are using the context however normal functions are not using this context and therefore none of the OTEL context (trace id etc) is propagated.

🔗 Related Issues

@maxkomarychev maxkomarychev changed the title fix: run blocking function fetcher using coroutine scope from the context fix: respect custom coroutine scope when calling blocking functions in a data fetcher May 27, 2025
@maxkomarychev maxkomarychev force-pushed the max/fetch-blocking-ctx branch 4 times, most recently from 1387547 to 492f993 Compare May 27, 2025 22:12
@zsiegel
Copy link

zsiegel commented May 27, 2025

👍🏻

@Samjin
Copy link
Contributor

Samjin commented Jun 5, 2025

Could you add test case for it?

@maxkomarychev
Copy link
Author

Sure!

…n a data fetcher

When running blocking function data fetcher it does not respect coroutine scope provided in the GraphQL context.

In my project we're creating custom a coroutine scope in order to propagate MDC and OTEL contexts in a coroutine like this:

```
CoroutineScope(MDCContext() + Dispatchers.IO + Context.current().asContextElement())
```

(here `Context` is an instance of OTEL context).

This works well in suspended functions since they are using the context however normal functions are not using this context and therefore none of the OTEL context (trace id etc) is propagated.
@maxkomarychev maxkomarychev force-pushed the max/fetch-blocking-ctx branch from 492f993 to 48a2b88 Compare June 9, 2025 15:27
@maxkomarychev
Copy link
Author

Added tests @Samjin

parameterValues: Map<KParameter, Any?>
): Any? = try {
val coroutineScope = environment.graphQlContext.getOrDefault(CoroutineScope(EmptyCoroutineContext))
runBlocking(coroutineScope.coroutineContext) {
Copy link
Contributor

@samuelAndalon samuelAndalon Jun 18, 2025

Choose a reason for hiding this comment

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

I do understand the reasoning behind this change, but think about it, running a blocking function in a coroutine could be catastrophic just because you need context propagation, there should be another way to deal with this issue.

I do not know your use case, but this issue requires a different solution, and for now, what I would advice is to mark the resolver/function as suspend, what is blocking inside your resolver ?

Copy link
Author

Choose a reason for hiding this comment

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

I do not know your use case, but this issue requires a different solution, and for now, what I would advice is to mark the resolver/function as suspend, what is blocking inside your resolver ?

Dataloaders don't play nice with suspended functions. Because of that all functions which resolve properties via dataloaders are not suspended and the trace breaks.

Copy link
Author

@maxkomarychev maxkomarychev Jun 19, 2025

Choose a reason for hiding this comment

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

I understand the concern of running blocking code in a coroutine but it's not dramatically worse than running it in an arbitrary thread, is it?

What if we supply another coroutine scope specifically for sync functions to avoid blocks on suspended functions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants