django / asgiref

ASGI specification and utilities

Home Page:https://asgi.readthedocs.io/en/latest/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ThreadPool execution should preserve contextvar context.

tomchristie opened this issue · comments

This is an implementation pattern, rather than something strictly for the ASGI spec.

Something that cropped up while implementing https://github.com/encode/sentry-asgi is that Python's ThreadPoolExecutor does not default to preserving the contextvars context, which really is what you want.

If you're using something like the Sentry SDK, then you needs contextvars in order to properly tie otherwise unrelated events together into, and you need it to continue working properly when threadpools are used.

Here's my fix in Starlette: encode/starlette#192

Other ASGI tools and frameworks will want to use something similar when running sync code in threadpools.

Links to core issue here: encode/starlette#191

Given that this is what sync_to_async does, are you suggesting we should apply it there? That's the only way Channels runs things in threadpools, I believe.

are you suggesting we should apply it there

I think it's the right way to do things if we want contextvars to work correctly.

See the contextvars PEP: https://www.python.org/dev/peps/pep-0567/#offloading-execution-to-other-threads
And this Python issue: https://bugs.python.org/issue34014

You could either wait until the issue manifests (eg. someone using sentry-asgi with Daphne / Channels, notices that in certain situations bits of logging are not being correctly tied in.) so that you've got some concrete evidence to work against, or just address it now on it's own merit.

FWIW we got bitten by this in our django-channels app and we're glad to see it fixed in the framework such that ContextVars just work as you'd expect them across that thread boundary. We used https://pypi.org/project/contextvars-executor/ and a

loop = asyncio.get_event_loop()
loop.set_default_executor(ContextVarExecutor(max_workers=int(os.environ.get("ASGI_THREADS", 1))))

to fix this for anything using asyncio internally, but I think this fix means we can get rid of that. +1 for Python changing this sooner rather than later!