Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BusError in uvloop within loop extract_stack for nested async_generators #611

Open
autumnjolitz opened this issue Jul 10, 2024 · 0 comments

Comments

@autumnjolitz
Copy link
Contributor

  • uvloop version:
  • 0.19.0 (tested 0.17.0 to 0.19.0)
  • Python version: 3.12.4
  • Platform: OSX Ventura 13.4/ Darwin 22.5.0
  • Can you reproduce the bug with PYTHONASYNCIODEBUG in env?:
  • This bug only occurs if -X dev or PYTHONDEVMODE=1 or PYTHONASYNCIODEBUG=1 or loop.set_debug(True)
  • Does uvloop behave differently from vanilla asyncio? How?:
  • Vanilla asyncio and uvloop with loop.set_debug(False) do not crash in nested async_generator .asend calls

I have a pattern called DeferredExecutor which basically allows one to write:

class Foo:
        @database(transaction=True, iterable=False)
        async def save(self, *args, **kwargs):
               # /snip
              rows = yield DeferredQuery("some sql")
              assert isinstance(rows, tuple)
              yield Return(...)

And have it be wrapped by a decorator that will call the async_generator, run the deferred queries against a DB and .asend( the result back into the generator, which will throw an StopAsyncIteration at the conclusion of the generator, and allows me to catch it and end the generators execution.

I am getting my database access object library to run on Python 3.12 (jumping from Python 3.7).

However, on Python 3.12, a particular integration test that uses a particularly reduced implementation:

class Tenant:
    @database(iterable=False, transaction=True)
    async def invite_users(cls, *tenant_user_pairs, instance=None):
        invites = (instance or cls).create_invites_for(*tenant_user_pairs)
        gen_event_loop = Invite.save.raw_iterable(*invites)
        result = None
        while True:
            result = await gen_event_loop.asend(result)
            if isinstance(result, Return):
                break
            result = yield result
        yield result # StopAsyncIteration gets thrown here

causes uvloop to crash with a Bus Error on OSX but only when loop.set_debug(True) (which is autoset via the environment variables or interpreter devmode)

Regular asyncio, debug or not, works just fine. If I patch the EventLoopPolicy to.set_debug(False) like:

def patch_uvloop():
    if uvloop_version_info < (99, 99, 99) and platform.system() == 'Darwin':
        cls = uvloop.EventLoopPolicy
        if hasattr(cls, '__patched__'):
            return cls
        class EventLoopPolicy(cls):
            __patched__ = True
            #  ARJ: so uvloop will crash the entire interpreter sometimes
            # and the crash originates on OSX. Disable debug mode
            # (which keeps it from touching uvloop's extract_stack() which 
            # explodes badly)

            def _loop_factory(self):
                loop = super()._loop_factory()
                loop.set_debug(False)
                return loop
        uvloop.EventLoopPolicy = EventLoopPolicy

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

Then the crashes never happen.

I do have lldb/gdb and the patience to recompile things with more debugging information (provided I know -how-) but a careful inspection of the following stack trace shows that it explodes after going into the extract_stack function of uvloop:

This does remind me of python/cpython#94694

I wonder if the extract_stack function is tolerant of encountering negative numbers in the offset extraction of the trace.

Stack report:
stack.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant