-
Notifications
You must be signed in to change notification settings - Fork 213
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
How to reset ContextVar in separate sync_to_async calls #267
Comments
What would you expect to happen - that everything shares the same context (both in and outside the sync_to_async/async_to_sync calls)? I believe that is what the intended effect is, but I didn't author the contextvar code in asgiref so I'd have to go re-learn it to see where this might be. |
Thanks for following up, Andrew! :) Yes, I think that's what I would expect. This is the simplest test I found to reproduce the issue: import contextvars
from asgiref.sync import sync_to_async
import pytest
foo = contextvars.ContextVar("foo")
@pytest.mark.asyncio
async def test_sync_to_async_contextvar_reset():
token = foo.set("bar")
await sync_to_async(foo.reset)(token)
> /mnt/data/Proyectos/third_party/asgiref/asgiref/sync.py(423)__call__()
422 context = contextvars.copy_context()
--> 423 child = functools.partial(self.func, *args, **kwargs)
424 func = context.run
ipdb> !context
<Context object at 0x7fcf31388f00>
ipdb> contextvars.copy_context()
<Context object at 0x7fcf30a76700>
ipdb> context2 = contextvars.copy_context()
ipdb> !context
<Context object at 0x7fcf31388f00>
ipdb> !context2
<Context object at 0x7fcf30a76700>
ipdb> contextvars.copy_context()
<Context object at 0x7fcf31394a40> |
Hm, interesting. I suspect whoever initially wrote this didn't test the Unless you want to dive into this and see if you can make it work "as intended", I'll have to see if I can get around to looking at this later in the month (I'm mostly taking a break from OSS at the moment) - at the very worst, I'd hope we can at least get better errors and document the intended behaviours. |
New-style middlewares were [introduced][django_1_10_changelog] in Django `1.10`, and also `settings.MIDDLEWARE_CLASSES` was [removed][django_2_0_changelog] in Django 2.0. This change migrates the Django middleware to conform with the new style. This is useful because it will help us solve the pending issue in open-telemetry#391. By having a single entrypoint to the middleware, `__call__`, which is [wrapped with `sync_to_async` just once][call_wrapped] for async requests, we avoid the [issue][asgiref_issue] where a `ContextVar` cannot be reset from a different context. With the current deprecated `MiddlewareMixin` way, both `process_request` and `process_response` were being [wrapped separately with `sync_to_async`][mixin_wrapped], which was the source of the mentioned issue. [django_1_10_changelog]: https://docs.djangoproject.com/en/3.2/releases/1.10/#new-style-middleware [django_2_0_changelog]: https://docs.djangoproject.com/en/3.2/releases/2.0/#features-removed-in-2-0 [call_wrapped]: https://github.com/django/django/blob/213850b4b9641bdcb714172999725ec9aa9c9e84/django/core/handlers/base.py#L54-L57 [mixin_wrapped]: https://github.com/django/django/blob/213850b4b9641bdcb714172999725ec9aa9c9e84/django/utils/deprecation.py#L137-L147 [asgiref_issue]: django/asgiref#267
New-style middlewares were [introduced][django_1_10_changelog] in Django `1.10`, and also `settings.MIDDLEWARE_CLASSES` was [removed][django_2_0_changelog] in Django 2.0. This change migrates the Django middleware to conform with the new style. This is useful because it will help us solve the pending issue in open-telemetry#391. By having a single entrypoint to the middleware, `__call__`, which is [wrapped with `sync_to_async` just once][call_wrapped] for async requests, we avoid the [issue][asgiref_issue] where a `ContextVar` cannot be reset from a different context. With the current deprecated `MiddlewareMixin` way, both `process_request` and `process_response` were being [wrapped separately with `sync_to_async`][mixin_wrapped], which was the source of the mentioned issue. [django_1_10_changelog]: https://docs.djangoproject.com/en/3.2/releases/1.10/#new-style-middleware [django_2_0_changelog]: https://docs.djangoproject.com/en/3.2/releases/2.0/#features-removed-in-2-0 [call_wrapped]: https://github.com/django/django/blob/213850b4b9641bdcb714172999725ec9aa9c9e84/django/core/handlers/base.py#L54-L57 [mixin_wrapped]: https://github.com/django/django/blob/213850b4b9641bdcb714172999725ec9aa9c9e84/django/utils/deprecation.py#L137-L147 [asgiref_issue]: django/asgiref#267
After filing https://code.djangoproject.com/ticket/32815, it was more evident that the issue I'm experiencing when trying to reset a
ContextVar
is related toasgiref
. However, I'm not sure about this being a bug or an implementation issue, so any feedback is appreciated.This issue has been raised in our efforts to add Django ASGI support to OpenTelemetry (open-telemetry/opentelemetry-python-contrib#391). OpenTelemetry uses a Django middleware class to implement both
process_request
andprocess_response
methods. As both of them are synchronous, they are converted withsync_to_async
and called separately (withthread_sensitive=True
). The first call sets theContextVar
value, and persists the token to reset it. The second call uses the persisted token to reset theContextVar
to its previous value.Tests added to
tests/test_sync_contextvars.py
, which reproduce the issue:Test traceback:
The text was updated successfully, but these errors were encountered: