python-trio / trio-asyncio

a re-implementation of the asyncio mainloop on top of Trio
Other
189 stars 38 forks source link

Explore interposition #30

Closed smurfix closed 6 years ago

smurfix commented 6 years ago

Interposition code, thanks to @Fuyukai and slightly embellished:

import types
@types.coroutine
def run_with_shim(fn, *args):
    """
    ;)
    """
    coro = fn(*args)
    # start the coroutine
    yielded = coro.send(None)
    while True:
        try:
            if isinstance(yielded, asyncio.Future):
                next_send = yield from trio_asyncio.run_future(yielded)
            else:
                next_send = yield yielded
        except BaseException as exc:
            p,a = coro.throw,exc
        else:
            p,a = coro.send,next_send
        try:
            yielded = p(a)
        except StopIteration as e:
            return e.value

Benchmarking shows an additional 10% slow-down for asyncio and no measurable change for trio, which is OK with me.

#!/usr/bin/python3
N=5000; D=0.001
async def chk(s, sleep):
    t1 = time()
    for x in range(N):
        await sleep(D)
    s = " "*(12-len(s))+s
    print("%12s %.6f" % (s,((time()-t1)/N-D)*1000))

import trio
from time import time
trio.run(chk, "trio", trio.sleep)

import asyncio
loop = asyncio.get_event_loop()
loop.run_until_complete(chk("asyncio",asyncio.sleep))

async def inner(p,*a):
    await p(*a)
import trio_asyncio
trio_asyncio.run(chk, "ta:trio", trio.sleep)
trio_asyncio.run(inner, run_with_shim, chk, "ta:s:trio", trio.sleep)
trio_asyncio.run(trio_asyncio.run_asyncio, chk, "ta:asyncio", asyncio.sleep)
trio_asyncio.run(inner, run_with_shim, chk, "ta:s:asyncio", asyncio.sleep)
smurfix commented 6 years ago

TODO: incorporate that into trio-asyncio and check whether the tests still work, esp. those related to cancellation semantics.

smurfix commented 6 years ago

… they don't, of course. This may or may not actually affect real-world code, so I'll include it, but it obviously can't be the default.

Fuyukai commented 6 years ago

The tests probably don't pass because the shim doesn't do things like set current_task etc which some libs rely on.

I also never tested it on anything more complex than a very basic sleep.

smurfix commented 6 years ago

@Fuyukai It doesn't need to, run_future already does that (by delegating execution to asyncio).

Lots of tests pass, and one doesn't need to keep track all those fiddly wrappers …

Fuyukai commented 6 years ago

When I tried aiohttp, it was complaining that there was no task context or something.

smurfix commented 6 years ago

Yeah, aiohttp is demanding about its perceived lack of task context. I've hit that problem last month (i.e. without your wrapper ;-) but no time yet to investigate further – besides, I'd rather spend time working on a real http solution for trio anyway.

njsmith commented 6 years ago

I believe that aiohttp depends on being able to do current_task().cancel() to implement timeouts (via the async-timeout package).

njsmith commented 6 years ago

(and obviously this doesn't work if we're actually in a trio task and haven't switched into an asyncio task.)

smurfix commented 6 years ago

Simple version added. Complicated cases still require distinct modes.