The following program behaves differently when monkeypatched, compared to when running under the Python stdlib. Using the stdlib contextvars, each asyncio task prints its own chosen uuid. When monkeypatched, all tasks print the same uuid after the first loop iteration.
gevent's contextvars work with gevent, not with asyncio. Just as asyncio's contextvars are not greenlet aware, gevent's contextvars are not asyncio-task aware. You generally can't mix-and-match the two.
Thanks for the clarification. Having now looked at both implementations, I think I see what you mean: melding the two would be hard to do from within gevent. We'd basically have to instrument every switch operation, storing the stack (linked-list, really) of PyContext objects from the interpreter state every time we switch into a new greenlet. So far as I can tell, there's no clean way to access the requisite information from within the Python interpreter, but a C extension with access to the PyThreadState object would make it straightforward.
I started looking at the greenlet library, and I realized that adding support for switching contextvars becomes straightforward as a patch within that codebase. It also makes sense that the functionality be implemented there: greenlet is meant to facilitate switching interpreter state in and out, and contextvars are implemented as an extension of the interpreter state. To that end, I'm thinking to send this branch as a PR upstream. Would you be interested in taking a look at that change and providing feedback?
I think I like the idea very much, and the basic implementation looks reasonable to me at a glance.
I'm a wee bit concerned, though, that this makes every greenlet its own "context" (if I understand correctly). It's sort of like an implicit monkey-patch. It would make certain patterns impossible (such as using two greenlets from a single asyncio task). I don't know how upstream will feel about that.
You're thinking of a situation where one asyncio task launches two greenlets, and they want to be able to call .set(value) on a ContextVar instance, and see each others' changes? I think the endorsed pattern there is for the launching code to call .set(dict()), and then launch two greenlets with copy_context().run. The two greenlets will then each fetch the dict using .get() on the ContextVar instance, and communicate through mutations of the shared dictionary object.
When I build gevent against a patched greenlet egg, the cython runtime loader emits this warning: greenlet.greenlet size changed, may indicate binary incompatibility, and my process segfaulted shortly thereafter. I have updated the PR upstream to put the new attribute at the end of the struct. I believe this avoids re-arranging fields incompatibly, but the warning remains. I think that to be safe against this kind of breakage, gevent may need to vendor greenlet, like it does libev and c-ares, or specify an exact version pin. But I'm guessing you've seen this before, based on this convenient comment:
191 greenlet_requires = [
192 # We need to watch our greenlet version fairly carefully,
193 # since we compile cython code that extends the greenlet object.
194 # Binary compatibility would break if the greenlet struct changes.
195 # (Which it did in 0.4.14 for Python 3.7)
196 'greenlet >= 0.4.16; platform_python_implementation=="CPython"',