Hi,

Trollius 0.4 now supports asyncio coroutines. I just found a bug when
an asyncio coroutine yields from a Trolius coroutine: bug in
CoroWrapper on Python < 3.4.1 when CoroWrapper is used with
yield-from.

In Tulip, CoroWrapper works around CPython bug #21209 with this code:

    def send(self, *value):
        # We use `*value` because of a bug in CPythons prior
        # to 3.4.1. See issue #21209 and test_yield_from_corowrapper
        # for details.  This workaround should be removed in 3.5.0.
        if len(value) == 1:
            value = value[0]
        return self.gen.send(value)

This workaroud doesn't work if you use asyncio coroutines with
trollius coroutines. See tests/test_asyncio.py in Trollius for such
code.

I modified Trollius to detect dynamically if Python has the yield-from
bug or not, and if it has the bug: get the last instruction of the
caller to check if it's yield-from...

    if YIELD_FROM_BUG:
        # For for CPython issue #21209: using "yield from" and a custom
        # generator, generator.send(tuple) unpacks the tuple instead of passing
        # the tuple unchanged. Check if the caller is a generator using "yield
        # from" to decide if the parameter should be unpacked or not.
        def send(self, *value):
            frame = sys._getframe()
            caller = frame.f_back
            if (caller.f_lasti != -1
                and caller.f_code.co_code[caller.f_lasti] != YIELD_FROM):
                value = value[0]
            return self.gen.send(value)
    else:
        def send(self, value):
            return self.gen.send(value)

Checking the bytecode if the caller is ugly and may be slow.
CoroWrapper is only used in debug mode, so performances are not really
important. I don't think that it's possible to workaround properly the
bug without checking the bytecode of the caller. In Trollius, some
coroutines may use yield (trollius coroutines), whereas others use
yield-from (asyncio coroutines).

I reproduced the bug in asyncio with this "border-line" coroutine:

@asyncio.coroutine
def gen4():
    fut = asyncio.Future()
    loop.call_soon(fut.set_result, (1,))
    # --- "yield from fut" using yield... ---
    for item in fut:
        x = yield item
    # --- "yield from fut" using yield... ---
    return x

gen4() returns 1 instead of (1,) if you have the yield-from bug.

Do you think that I should port my better workaround from Trollius to Tulip?

The yield-from was fixed in Python 3.4.1, but it's not fixed in Python
3.3. Python 3.3 only accepts security bug fixes, and anyway we should
support Python 3.3 with the bug.

Victor

Reply via email to