Switch locals to be based on ContextVars#1778
Conversation
02284ef to
9254935
Compare
|
For context there is a discussion on discord about keeping |
|
Need to figure out what to pass to Flask-SQLAlchemy's scoped session, since it's using |
|
Looks like Gevent now patches contextvars: gevent/gevent#1512 Can't find exactly what release this was, it's not in the changelog, but there is a mention of contextvars in 20.04, so at least since April. |
|
@untitaker I know you've been working on contextvars in Sentry recently, any chance you could review this? |
|
I would definetly try to detect gevent and eventlet monkeypatching because if you run Flask under Python 3.7 with gunicorn you're gonna get a broken out of the box experience with this PR. Here's the impl in the Sentry SDK: https://github.com/getsentry/sentry-python/blob/36ed64eb0f65a0abae83fd5eacf1a524e2d17a37/sentry_sdk/utils.py#L754 Here's the documentation: https://docs.sentry.io/platforms/python/contextvars/ Django also has its own implementation under |
|
I'm still figuring out if asgiref.local or aiocontextvars from PyPI is a better approach for having context locals in Python 3.6 that work with asyncio. I think it's nice that aiocontextvars can never go out of sync with contextvars (as seems to be the case with asgiref.local), at the same time aiocontextvars monkeypatches contextvars and asgiref.local doesn't. |
|
Given that 3.6 is on its way out, and async support is very optional right now, maybe it would be ok to have an optional dependency on aiocontextvars, or just keep the other local implementation for 3.6 and say that async support is 3.7+ only. |
|
Actually, it looks like Sentry is doing something similar to what this PR is doing, falling back to |
|
Sentry has two things that differentiate it from this PR:
|
|
Thanks! @pgjones looks like there's some work to do before merging this. |
|
@pgjones can you take another look at the feedback here? |
|
We have an official summary of this now in Sentry docs: https://docs.sentry.io/platforms/python/troubleshooting/ @pgjones if you are no longer planning on pursuing this lmk I can probably continue your work, if necessary |
|
Also note that asgiref works fine now, so perhaps it makes sense to just hard-require asgiref in Flask (and ahve it be an extra in werkzeug) |
|
How about this version? It follows the sentry checks to see if ContextVars are usable, if not it will fall back to the current implementation. |
|
Yeah either something like that or use asgiref.local for everything
…On Thu, Oct 29, 2020, at 19:15, Phil Jones wrote:
How about this version? It follows the sentry checks to see if ContextVars are usable, if not it will fall back to the current implementation.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#1778 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAGMPRIESZZHHC7A7NEDQ2DSNGWKZANCNFSM4LVVT5GQ>.
|
|
Note: I've asked on the eventlet issue tracker about ContextVars. Some extra thoughts:
|
|
Is it worth supporting gevent<20.5? If we leave out the check and just use a There should probably be a test that use async to demonstrate that these work in coroutines, although the current test that uses threads is prone to failure in CI. |
If you mean a stdlib ContextVar it would result in locals not being local to green threads, and hence things working very weirdly (leaking between requests).
I don't think we can, as at the moment it would be used for all eventlet implementations, and hence warn the user when they have nothing to worry about. The unexpected interactions would only be if the user is using asyncio/trio/curio and eventlet/gevent which I think is unlikely and in addition detecting to warn is too difficult and not worth the effort. |
Whilst not used this way with Quart, if a Local is created and assigned a value before being accessed across tasks (requests) it would erroneously share the values across the tasks. This is because the dictionary would be mutated, rather than replaced - hence the copy fix. See also pallets/werkzeug#1778 for the future replacement of Quart's locals.
10b8d27 to
6bd6618
Compare
|
I always mix up that the import is from Greenlet, not Gevent, it's shared by both Gevent and Eventlet. So if either library doesn't patch We should plan on removing the fallback once we drop Python 3.6 and Eventlet supports context vars. |
Yep, fully agree. |
untitaker
left a comment
There was a problem hiding this comment.
This looks good. For future work, I would forbid use of ASGI if the real contextvar is not used. Due to security reasons.
|
I'm reading the
I'm not sure of the timeline, but Werkzeug probably originally had to support Python 2.3, which didn't have |
I'm not sure that things would be easier to reason about in the Werkzeug context if this was done. |
|
The way locals are used in Werkzeug and Flask is to hold one value (or one list). Werkzeug would still have |
ContextVars have been introduced into Python 3.7 and are effectively the same as Locals in Werkzeug (although a different API). They should work in a Greenlet, and threading, context as before but also now in an asyncio, or other async/await context. The __storage__ attribute has been kept for backwards compatibility (even though it is a dunder it seems it is used, e.g. in tests). The ident_function though must be deprecated as this is now handled by the ContextVar. This may cause some backwards incompatibility. This is based on the Quart local implementation. If ContextVars aren't available (Python 3.6) or there is an issue using them i.e. Gevent/Eventlet have no monkey patched them then the local will fall back to the previous (green) thread identity based version.
|
This PR is IMO missing a facility to check if the real contextvars is being used for locals. I am not sure what the plans are, but I just want to reiterate my suggestion to forbid any usage of ASGI without real contextvars being available. Even Flask supports only 3.7, older gevent/eventlet can still mess locals up. |
|
So Quart already forbids this (3.7+), but for Flask maybe we should add a Py version check in the ensure wrapper? |
|
@pgjones right but since the werkzeug impl checks for broken gevent/eventlet it can happen that werkzeug ends up chosing not to use contextvars even if on Python 3.7. So a version check would be insufficient. The check would have to be closer to |
|
@untitaker do you think it is now sufficient to check if |
|
I think so, but not sure if that flag exists on older greenlet?
…On Wed, Feb 10, 2021, at 10:12, Phil Jones wrote:
@untitaker <https://github.com/untitaker> do you think it is now sufficient to check if `from greenlet import GREENLET_USE_CONTEXT_VARS; GREENLET_USE_CONTEXT_VARS is True`? Rather than the monkeypatching checks? (See contextvar support in Greenlet).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#1778 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAGMPRND6F2R2XDS2Y2DFQTS6JEZNANCNFSM4LVVT5GQ>.
|
|
I think I'm ok with "if greenlet isn't new enough, fall back to fake implementation" now, and making it "raise RuntimeError" once we drop 3.6. Since gevent still patches on older greenlets, we could still have that as an extra check too. |
|
My stance is that Flask should not permit the application to run if there's a chance its locals do not provide correct isolation, as that can cause sessions to bleed over. Irrespective of the python version that can happen when Flask serves requests via ASGI while contextvars are not used. An old gevent can induce this state on 3.7 too.
…On Wed, Feb 10, 2021, at 15:50, David Lord wrote:
I think I'm ok with "if greenlet isn't new enough, fall back to fake implementation" now, and making it "raise RuntimeError" once we drop 3.6. Since gevent still patches on older greenlets, we could still have that as an extra check too.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#1778 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAGMPRJTQLO5FVTGHSNLTIDS6KMM5ANCNFSM4LVVT5GQ>.
|
|
And to be clear gevent in ASGI is such an unpopular setup that I'd hard error regardless of python version support
…On Wed, Feb 10, 2021, at 16:14, Markus Unterwaditzer wrote:
My stance is that Flask should not permit the application to run if there's a chance its locals do not provide correct isolation, as that can cause sessions to bleed over. Irrespective of the python version that can happen when Flask serves requests via ASGI while contextvars are not used. An old gevent can induce this state on 3.7 too.
On Wed, Feb 10, 2021, at 15:50, David Lord wrote:
>
> I think I'm ok with "if greenlet isn't new enough, fall back to fake implementation" now, and making it "raise RuntimeError" once we drop 3.6. Since gevent still patches on older greenlets, we could still have that as an extra check too.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub <#1778 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAGMPRJTQLO5FVTGHSNLTIDS6KMM5ANCNFSM4LVVT5GQ>.
>
|
|
I don't think there are any plans for Flask to work as an ASGI application. I will add a hard check to the async (ensure_sync) proposal though. See #2039 for a proposed greenlet update. |
ContextVarwas introduced in Python 3.7 and is effectively the same asLocalin Werkzeug (although a different API). They should work in Greenlet and threading contexts as before but also now in asyncio or other async/await contexts.The
__storage__attribute has been kept for backwards compatibility (even though it is a dunder it seems it is used, e.g. in tests).The
ident_functhough must be deprecated as this is now handled by theContextVar. This may cause some backwards incompatibility.