miguelgrinberg / greenletio

Asyncio integration with sync code using greenlets.
MIT License
150 stars 8 forks source link

support contextvars #4

Closed moriyoshi closed 3 years ago

moriyoshi commented 3 years ago

This patch allows propagating variables in the calling context into greenlets.

miguelgrinberg commented 3 years ago

Does this work at all? The contextvars module is not compatible with greenlets, I believe. Or is it?

moriyoshi commented 3 years ago

CPython's contextvars are per-OS-thread contexts that flow along the call graphs. Semantically speaking, a different context should be created on each greenlet and it's exactly what gevent.contextvarsdoes. However, what is actually needed in asyncio/greenlet interoperation is to propagate the same lineage of contexts beyond the dispatchers that should reside on the same thread. So, I don't think we have to treat our case specially as long as the context gets restored in each dispatch.

miguelgrinberg commented 3 years ago

You are oversimplifying the impact contextvars will have on greenlets. Try the following test:

import asyncio
from greenletio import async_, await_
import contextvars

var = contextvars.ContextVar('var')

async def asleep(delay):
    var.set(123)
    for i in range(10):
        await asyncio.sleep(delay)
        print('async sleep', var.get())

@async_
def sleep(delay):
    var.set(321)
    for i in range(10):
        await_(asyncio.sleep(delay))
        print('greenlet sleep', var.get())

async def main():
    await asleep(0.1)
    await sleep(0.1)

asyncio.run(main())

The call to asleep() works great, as this is 100% asyncio based. Then the call to sleep() fails in a horrible way, even though it does the same. I'm running this on your PR branch.