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

Possible race condition in get_asynclib when threading #210

Closed
rmorshea opened this issue Feb 24, 2021 · 25 comments
Closed

Possible race condition in get_asynclib when threading #210

rmorshea opened this issue Feb 24, 2021 · 25 comments

Comments

@rmorshea
Copy link

rmorshea commented Feb 24, 2021

I'm not quite sure how to reproduce and it's also possible I'm doing something wrong that's causing this. I figured I'd open this just so anyone else that might be experiencing something similar has something to try as a fix, so feel free to close if there's not enough info or shouldn't be solved in AnyIO.

Here's the traceback I was seeing when trying to use anyio from a thread:

Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/threading.py", line 926, in _bootstrap_inner
    self.run()
  File "/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
...
...
...
  File "/opt/hostedtoolcache/Python/3.7.10/x64/lib/python3.7/site-packages/anyio/_core/_tasks.py", line 74, in create_task_group
    return get_asynclib().TaskGroup()
AttributeError: module 'anyio._backends._asyncio' has no attribute 'TaskGroup'

This seemed very weird until I got another traceback claiming that anyio._backends._asyncio was partially initialized which clued me into the fact that this was a race condition.

The solution was just to import anyio._backends._asyncio at the top of my file before starting the thread that imports anyio.

@agronholm
Copy link
Owner

Can you give me any clues as how to repro this? How are you using threads?

@rmorshea
Copy link
Author

rmorshea commented Feb 24, 2021

My best guess is that the race condition occurs when two threads check whether anyio._backends._asyncio (M) needs to be imported...

The first thread (T1) checks sys.modules, finds M is absent, and begins to import it. Then the following thread (T2) arrives at sys.modules to find that M is present but, unbeknownst to T2, the module is only partially initialized because T1 has not finished importing it. Finally T2 attempts to use M, which is partially imported, under the assumption that M has actually been fully initialized, thus resulting in an AttributeError when T2 accesses a resource that has not been defined yet.

Given that this is in fact the problem, I can see two solutions:

  1. eagerly import all available backends
  2. insert a lock around access to the backend modules while they are being imported

@agronholm
Copy link
Owner

Are you by any change spawning threads as side effects of imports?

@rmorshea
Copy link
Author

I suppose it's possible that a package I depend on does, but in my code, I am not.

@agronholm
Copy link
Owner

Could you check?

@rmorshea
Copy link
Author

It would be easier if I tried to patch anyio with my suggested changes, and then check if the race condition still happens. It occurs pretty frequently (like 4 out 5 times) so I shouldn't need to use a high sample size to be confident in the fix.

@agronholm
Copy link
Owner

I guess it wouldn't hurt. But import conflicts like this simply shouldn't happen unless there was a circular import somewhere.

@rmorshea
Copy link
Author

I'm not sure that's true because get_asynclib is lazily importing modules. My guess is that this is where the race condition manifests:

return import_module(modulename)

@agronholm
Copy link
Owner

Are you running async code in multiple threads?

@rmorshea
Copy link
Author

Yes

@agronholm
Copy link
Owner

That is highly unorthodox. Can you describe why this is done? I can see the imports being a problem there.

@rmorshea
Copy link
Author

I agree.

It's a little hard to explain, but at a high level I have an async framework for creating interactive UI elements and it needs to work with the Flask web server (which is not async). To do that I have to create to different threads each of which is running an event loop. I can point you to the code in question, but I don't think that will be very helpful.

@agronholm
Copy link
Owner

Please do point me to that code, it just might be.

@rmorshea
Copy link
Author

Put even more succinctly, I'm trying to make an async framework play nicely with a sync framework and it requires a bit of nastiness with threads.

@agronholm
Copy link
Owner

Sure, but that's where blocking portals come in.

@rmorshea
Copy link
Author

@rmorshea
Copy link
Author

blocking portals?

@agronholm
Copy link
Owner

@agronholm
Copy link
Owner

By the way, code like this will likely lead to problems: https://github.com/idom-team/idom/blob/main/idom/core/dispatcher.py#L37-L39
If the context manager is exited in a different task than where it was entered, it will cause all kinds of weird bugs, and trio checks for this explicitly and raises an error if detected. AnyIO doesn't yet but even before this I thought it would be a really good idea to do so because I've been running into the same issues myself when working on another library that uses AnyIO.

@agronholm
Copy link
Owner

So normally would have only one thread running the event loop, and then use blocking portals to call async code in that event loop from worker threads, thus avoiding any concurrency issues with dynamic imports.

@rmorshea
Copy link
Author

This is really helpful. I don't have time to fully understand or try these suggestions out now, but I'll definitely be looking into them. Feel free to close this for now. I'll re-open if I'm unable to figure it out on my own.

@rmorshea
Copy link
Author

Thanks!

@smurfix
Copy link
Collaborator

smurfix commented Feb 25, 2021

NB, another possibility is to use quart (or quart-trio). It's async and mostly-compatible with Flask. No more threads.

@rmorshea
Copy link
Author

Definitely an option for others. Unfortunately in my case though I'm trying to integrate with Dash which uses Flask.

@Jaza
Copy link

Jaza commented Mar 23, 2023

I think I've been experiencing this same issue with a FastAPI app. The app is kicked off with this code, which is in myapp/web.py:

import asyncio
from threading import Thread

from gunicorn.app.wsgiapp import WSGIApplication


class StandaloneApplication(WSGIApplication):
    def __init__(self, app_uri, options=None):
        self.options = options or {}
        self.app_uri = app_uri
        super().__init__()

    def load_config(self):
        config = {
            key: value
            for key, value in self.options.items()
            if key in self.cfg.settings and value is not None
        }
        for key, value in config.items():
            self.cfg.set(key.lower(), value)


def run_web():
    options = {
        "bind": "myhostname:8765",
        "workers": 3,
        "worker_class": "uvicorn.workers.UvicornWorker",
    }
    StandaloneApplication("myapp.main:app", options).run()


async def _run_asyncio():
    await thing_that_runs_in_background_as_long_as_this_process_is_alive()


def run_asyncio():
    asyncio.run(_run_asyncio())


def run():
    asyncio_thread = Thread(target=run_asyncio, daemon=True)
    asyncio_thread.start()
    run_web()
    asyncio_thread.join()

One of my stack traces looks like this:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
           │         │     └ {'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': <zipimporter object "/app/myapp/web.pyz/">...
           │         └ <code object <module> at 0x7f065f77eef0, file "/app/myapp/web.pyz/__main__.py", line 1>
           └ <function _run_code at 0x7f065f52d2d0>
  File "/usr/local/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
         │     └ {'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': <zipimporter object "/app/myapp/web.pyz/">...
         └ <code object <module> at 0x7f065f77eef0, file "/app/myapp/web.pyz/__main__.py", line 1>
  File "/app/myapp/web.pyz/__main__.py", line 3, in <module>
  File "/app/myapp/web.pyz/_bootstrap/__init__.py", line 253, in bootstrap
  File "/app/myapp/web.pyz/_bootstrap/__init__.py", line 38, in run
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/myapp/web.py", line 63, in run
    run_web()
    └ <function run_web at 0x7f065eda4dc0>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/myapp/web.py", line 27, in run_web
    StandaloneApplication(app_uri, options).run()
    │                     │        └ {'bind': 'myhostname:8765', 'workers': 3, 'worker_class': 'uvicorn.workers.UvicornWorker'}
    │                     └ 'myapp.main:app'
    └ <class 'gunicorn_uvicorn_runner.core.StandaloneApplication'>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/app/base.py", line 231, in run
    super().run()
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/app/base.py", line 72, in run
    Arbiter(self).run()
    │       └ <gunicorn_uvicorn_runner.core.StandaloneApplication object at 0x7f065c90c7c0>
    └ <class 'gunicorn.arbiter.Arbiter'>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/arbiter.py", line 202, in run
    self.manage_workers()
    │    └ <function Arbiter.manage_workers at 0x7f065e13b0a0>
    └ <gunicorn.arbiter.Arbiter object at 0x7f065c90c850>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/arbiter.py", line 551, in manage_workers
    self.spawn_workers()
    │    └ <function Arbiter.spawn_workers at 0x7f065e13b1c0>
    └ <gunicorn.arbiter.Arbiter object at 0x7f065c90c850>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/arbiter.py", line 622, in spawn_workers
    self.spawn_worker()
    │    └ <function Arbiter.spawn_worker at 0x7f065e13b130>
    └ <gunicorn.arbiter.Arbiter object at 0x7f065c90c850>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/arbiter.py", line 589, in spawn_worker
    worker.init_process()
    │      └ <function UvicornWorker.init_process at 0x7f065c7c4af0>
    └ <uvicorn.workers.UvicornWorker object at 0x7f065ddacb20>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/uvicorn/workers.py", line 66, in init_process
    super(UvicornWorker, self).init_process()
          │              └ <uvicorn.workers.UvicornWorker object at 0x7f065ddacb20>
          └ <class 'uvicorn.workers.UvicornWorker'>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/workers/base.py", line 142, in init_process
    self.run()
    │    └ <function UvicornWorker.run at 0x7f065c7c4ca0>
    └ <uvicorn.workers.UvicornWorker object at 0x7f065ddacb20>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/uvicorn/workers.py", line 83, in run
    return asyncio.run(self._serve())
           │       │   │    └ <function UvicornWorker._serve at 0x7f065c7c4c10>
           │       │   └ <uvicorn.workers.UvicornWorker object at 0x7f065ddacb20>
           │       └ <function run at 0x7f065eda5240>
           └ <module 'asyncio' from '/usr/local/lib/python3.10/asyncio/__init__.py'>
  File "/usr/local/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
           │    │                  └ <coroutine object UvicornWorker._serve at 0x7f065bbfbc30>
           │    └ <method 'run_until_complete' of 'uvloop.loop.Loop' objects>
           └ <uvloop.Loop running=True closed=False debug=False>
> File "/root/.shiv/web.pyz_A1B2C3/site-packages/uvicorn/protocols/http/httptools_impl.py", line 404, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
                   └ <uvicorn.middleware.proxy_headers.ProxyHeadersMiddleware object at 0x7f065cb4d3c0>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
                 │    │   │      │        └ <bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7f065c9049...
                 │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7f065c9...
                 │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
                 │    └ <fastapi.applications.FastAPI object at 0x7f065d0c6710>
                 └ <uvicorn.middleware.proxy_headers.ProxyHeadersMiddleware object at 0x7f065cb4d3c0>
 File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/applications.py", line 270, in __call__
    await super().__call__(scope, receive, send)
                           │      │        └ <bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7f065c9049...
                           │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7f065c9...
                           └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/applications.py", line 124, in __call__
    await self.middleware_stack(scope, receive, send)
          │    │                │      │        └ <bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7f065c9049...
          │    │                │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7f065c9...
          │    │                └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <starlette.middleware.errors.ServerErrorMiddleware object at 0x7f065bbb2a70>
          └ <fastapi.applications.FastAPI object at 0x7f065d0c6710>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
          │    │   │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x7f0658208d30>
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7f065c9...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <my_logging.middleware.RequestIdMiddleware object at 0x7f065bbb2ad0>
          └ <starlette.middleware.errors.ServerErrorMiddleware object at 0x7f065bbb2a70>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/my_logging/middleware.py", line 21, in __call__
    await self.app(scope, receive, send)
          │    │   │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x7f0658208d30>
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7f065c9...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <myapp.core.middleware.FilterBlankQueryParamsMiddleware object at 0x7f065bbb2b30>
          └ <my_logging.middleware.RequestIdMiddleware object at 0x7f065bbb2ad0>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/myapp/core/middleware.py", line 23, in __call__
    await self.app(scope, receive, send)
          │    │   │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x7f0658208d30>
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7f065c9...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <content_size_limit_asgi.middleware.ContentSizeLimitMiddleware object at 0x7f065cd78a30>
          └ <myapp.core.middleware.FilterBlankQueryParamsMiddleware object at 0x7f065bbb2b30>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/content_size_limit_asgi/middleware.py", line 56, in __call__
    await self.app(scope, wrapper, send)
          │    │   │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x7f0658208d30>
          │    │   │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7f0658209360>
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <starlette.middleware.exceptions.ExceptionMiddleware object at 0x7f065cd79270>
          └ <content_size_limit_asgi.middleware.ContentSizeLimitMiddleware object at 0x7f065cd78a30>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/middleware/exceptions.py", line 75, in __call__
    raise exc
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/middleware/exceptions.py", line 64, in __call__
    await self.app(scope, receive, sender)
          │    │   │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x7f0658209120>
          │    │   │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7f0658209360>
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <fastapi.middleware.asyncexitstack.AsyncExitStackMiddleware object at 0x7f065cd78a00>
          └ <starlette.middleware.exceptions.ExceptionMiddleware object at 0x7f065cd79270>
:File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
          │    │   │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x7f0658209120>
          │    │   │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7f0658209360>
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <fastapi.routing.APIRouter object at 0x7f065d0c79a0>
          └ <fastapi.middleware.asyncexitstack.AsyncExitStackMiddleware object at 0x7f065cd78a00>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/routing.py", line 680, in __call__
    await route.handle(scope, receive, send)
          │     │      │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x7f0658209120>
          │     │      │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7f0658209360>
          │     │      └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │     └ <function Route.handle at 0x7f065bb83010>
          └ <fastapi.routing.APIRoute object at 0x7f065bbb3190>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/routing.py", line 275, in handle
    await self.app(scope, receive, send)
          │    │   │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x7f0658209120>
          │    │   │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7f0658209360>
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <function request_response.<locals>.app at 0x7f065bc17f40>
          └ <fastapi.routing.APIRoute object at 0x7f065bbb3190>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/routing.py", line 65, in app
    response = await func(request)
                     │    └ <starlette.requests.Request object at 0x7f065c93d510>
                     └ <function get_request_handler.<locals>.app at 0x7f065bc17eb0>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/routing.py", line 221, in app
    solved_result = await solve_dependencies(
                          └ <function solve_dependencies at 0x7f065bb81900>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/dependencies/utils.py", line 504, in solve_dependencies
    solved_result = await solve_dependencies(
                          └ <function solve_dependencies at 0x7f065bb81900>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/dependencies/utils.py", line 535, in solve_dependencies
    solved = await run_in_threadpool(call, **sub_values)
                   │                 │       └ {}
                   │                 └ <functools._lru_cache_wrapper object at 0x7f065bbe49e0>
                   └ <function run_in_threadpool at 0x7f065c86a200>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/concurrency.py", line 41, in run_in_threadpool
    return await anyio.to_thread.run_sync(func, *args)
                 │     │         │        │      └ ()
                 │     │         │        └ <functools._lru_cache_wrapper object at 0x7f065bbe49e0>
                 │     │         └ <function run_sync at 0x7f065e2ae680>
                 │     └ <module 'anyio.to_thread' from '/root/.shiv/web.pyz_A1B2C3/site-pac...
                 └ <module 'anyio' from '/root/.shiv/web.pyz_A1B2C3/site-packages/anyi...
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/anyio/to_thread.py", line 31, in run_sync
    return await get_asynclib().run_sync_in_worker_thread(
                 └ <function get_asynclib at 0x7f065e2ae0e0>
  File "/root/.shiv/web.pyz_A1B2C3/site-packages/anyio/_backends/_asyncio.py", line 912, in run_sync_in_worker_thread
    async with (limiter or current_default_thread_limiter()):
                └ None
NameError: name 'current_default_thread_limiter' is not defined

And another one looks like this:

Traceback (most recent call last):

  File "/usr/local/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
           │         │     └ {'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': <zipimporter object "/app/myapp/web.pyz/">, ...
           │         └ <code object <module> at 0x7ff9a6b0ace0, file "/app/myapp/web.pyz/__main__.py", line 1>
           └ <function _run_code at 0x7ff9a6872b90>
  File "/usr/local/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
         │     └ {'__name__': '__main__', '__doc__': None, '__package__': '', '__loader__': <zipimporter object "/app/myapp/web.pyz/">, ...
         └ <code object <module> at 0x7ff9a6b0ace0, file "/app/myapp/web.pyz/__main__.py", line 1>

  File "/app/myapp/web.pyz/__main__.py", line 3, in <module>

  File "/app/myapp/web.pyz/_bootstrap/__init__.py", line 253, in bootstrap

  File "/app/myapp/web.pyz/_bootstrap/__init__.py", line 38, in run

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/myapp/web.py", line 59, in run
    run_web()
    └ <function run_web at 0x7ff9a66ca4d0>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/myapp/web.py", line 23, in run_web
    StandaloneApplication(app_uri, options).run()
    │                     │        └ {'bind': 'myhostname:8765', 'workers': 3, 'worker_class': 'uvicorn.workers.UvicornWorker'}
    │                     └ 'myapp.main:app'
    └ <class 'gunicorn_uvicorn_runner.core.StandaloneApplication'>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/app/base.py", line 231, in run
    super().run()

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/app/base.py", line 72, in run
    Arbiter(self).run()
    │       └ <gunicorn_uvicorn_runner.core.StandaloneApplication object at 0x7ff9a6affa30>
    └ <class 'gunicorn.arbiter.Arbiter'>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/arbiter.py", line 202, in run
    self.manage_workers()
    │    └ <function Arbiter.manage_workers at 0x7ff9a55dad40>
    └ <gunicorn.arbiter.Arbiter object at 0x7ff9a6afc550>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/arbiter.py", line 551, in manage_workers
    self.spawn_workers()
    │    └ <function Arbiter.spawn_workers at 0x7ff9a55dae60>
    └ <gunicorn.arbiter.Arbiter object at 0x7ff9a6afc550>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/arbiter.py", line 622, in spawn_workers
    self.spawn_worker()
    │    └ <function Arbiter.spawn_worker at 0x7ff9a55dadd0>
    └ <gunicorn.arbiter.Arbiter object at 0x7ff9a6afc550>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/arbiter.py", line 589, in spawn_worker
    worker.init_process()
    │      └ <function UvicornWorker.init_process at 0x7ff9a3c7dea0>
    └ <uvicorn.workers.UvicornWorker object at 0x7ff9a49569b0>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/uvicorn/workers.py", line 66, in init_process
    super(UvicornWorker, self).init_process()
          │              └ <uvicorn.workers.UvicornWorker object at 0x7ff9a49569b0>
          └ <class 'uvicorn.workers.UvicornWorker'>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/gunicorn/workers/base.py", line 142, in init_process
    self.run()
    │    └ <function UvicornWorker.run at 0x7ff9a3c7e0e0>
    └ <uvicorn.workers.UvicornWorker object at 0x7ff9a49569b0>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/uvicorn/workers.py", line 98, in run
    return asyncio.run(self._serve())
           │       │   │    └ <function UvicornWorker._serve at 0x7ff9a3c7e050>
           │       │   └ <uvicorn.workers.UvicornWorker object at 0x7ff9a49569b0>
           │       └ <function run at 0x7ff9a5d7dcf0>
           └ <module 'asyncio' from '/usr/local/lib/python3.10/asyncio/__init__.py'>

  File "/usr/local/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
           │    │                  └ <coroutine object UvicornWorker._serve at 0x7ff9a2a589e0>
           │    └ <method 'run_until_complete' of 'uvloop.loop.Loop' objects>
           └ <uvloop.Loop running=True closed=False debug=False>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/uvicorn/protocols/http/httptools_impl.py", line 436, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
                   └ <uvicorn.middleware.proxy_headers.ProxyHeadersMiddleware object at 0x7ff9a41f3700>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
                 │    │   │      │        └ <bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7ff9a42fd2...
                 │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7ff9a42...
                 │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
                 │    └ <fastapi.applications.FastAPI object at 0x7ff9a433be50>
                 └ <uvicorn.middleware.proxy_headers.ProxyHeadersMiddleware object at 0x7ff9a41f3700>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/applications.py", line 276, in __call__
    await super().__call__(scope, receive, send)
                           │      │        └ <bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7ff9a42fd2...
                           │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7ff9a42...
                           └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
          │    │                │      │        └ <bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7ff9a42fd2...
          │    │                │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7ff9a42...
          │    │                └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <starlette.middleware.errors.ServerErrorMiddleware object at 0x7ff9a2e97e80>
          └ <fastapi.applications.FastAPI object at 0x7ff9a433be50>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
          │    │   │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x7ff9a2952200>
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7ff9a42...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <my_logging.middleware.RequestIdMiddleware object at 0x7ff9a2e97ee0>
          └ <starlette.middleware.errors.ServerErrorMiddleware object at 0x7ff9a2e97e80>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/my_logging/middleware.py", line 21, in __call__
    await self.app(scope, receive, send)
          │    │   │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x7ff9a2952200>
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7ff9a42...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <myapp.core.middleware.FilterBlankQueryParamsMiddleware object at 0x7ff9a2e97f40>
          └ <my_logging.middleware.RequestIdMiddleware object at 0x7ff9a2e97ee0>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/myapp/core/middleware.py", line 23, in __call__
    await self.app(scope, receive, send)
          │    │   │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x7ff9a2952200>
          │    │   │      └ <bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x7ff9a42...
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <content_size_limit_asgi.middleware.ContentSizeLimitMiddleware object at 0x7ff9a2e94340>
          └ <myapp.core.middleware.FilterBlankQueryParamsMiddleware object at 0x7ff9a2e97f40>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/content_size_limit_asgi/middleware.py", line 56, in __call__
    await self.app(scope, wrapper, send)
          │    │   │      │        └ <function ServerErrorMiddleware.__call__.<locals>._send at 0x7ff9a2952200>
          │    │   │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7ff9a2952560>
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <starlette.middleware.exceptions.ExceptionMiddleware object at 0x7ff9a2e94310>
          └ <content_size_limit_asgi.middleware.ContentSizeLimitMiddleware object at 0x7ff9a2e94340>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
          │    │   │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x7ff9a2952290>
          │    │   │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7ff9a2952560>
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <fastapi.middleware.asyncexitstack.AsyncExitStackMiddleware object at 0x7ff9a2e942e0>
          └ <starlette.middleware.exceptions.ExceptionMiddleware object at 0x7ff9a2e94310>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e

File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
          │    │   │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x7ff9a2952290>
          │    │   │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7ff9a2952560>
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <fastapi.routing.APIRouter object at 0x7ff9a4338c10>
          └ <fastapi.middleware.asyncexitstack.AsyncExitStackMiddleware object at 0x7ff9a2e942e0>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
          │     │      │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x7ff9a2952290>
          │     │      │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7ff9a2952560>
          │     │      └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │     └ <function Route.handle at 0x7ff9a2ef0ee0>
          └ APIRoute(path='/myapp/v1/my-route/', name='my_route', methods=['GET'])

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
          │    │   │      │        └ <function ExceptionMiddleware.__call__.<locals>.sender at 0x7ff9a2952290>
          │    │   │      └ <function ContentSizeLimitMiddleware.receive_wrapper.<locals>.inner at 0x7ff9a2952560>
          │    │   └ {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.0', 'server': ('myhostname', 8765), 'cl...
          │    └ <function request_response.<locals>.app at 0x7ff9a2b3f2e0>
          └ APIRoute(path='/myapp/v1/my-route/', name='my_route', methods=['GET'])

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
                     │    └ <starlette.requests.Request object at 0x7ff9a42fc460>
                     └ <function get_request_handler.<locals>.app at 0x7ff9a2b3f250>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/routing.py", line 227, in app
    solved_result = await solve_dependencies(
                          └ <function solve_dependencies at 0x7ff9a2e8b400>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/dependencies/utils.py", line 592, in solve_dependencies
    solved_result = await solve_dependencies(
                          └ <function solve_dependencies at 0x7ff9a2e8b400>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/fastapi/dependencies/utils.py", line 623, in solve_dependencies
    solved = await run_in_threadpool(call, **sub_values)
                   │                 │       └ {}
                   │                 └ <functools._lru_cache_wrapper object at 0x7ff9a2f28d50>
                   └ <function run_in_threadpool at 0x7ff9a3bfc1f0>

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/starlette/concurrency.py", line 41, in run_in_threadpool
    return await anyio.to_thread.run_sync(func, *args)
                 │     │         │        │      └ ()
                 │     │         │        └ <functools._lru_cache_wrapper object at 0x7ff9a2f28d50>
                 │     │         └ <function run_sync at 0x7ff9a59ce4d0>
                 │     └ <module 'anyio.to_thread' from '/root/.shiv/web.pyz_A1B2C3/site-pac...
                 └ <module 'anyio' from '/root/.shiv/web.pyz_A1B2C3/site-packages/anyi...

  File "/root/.shiv/web.pyz_A1B2C3/site-packages/anyio/to_thread.py", line 31, in run_sync
    return await get_asynclib().run_sync_in_worker_thread(
                 └ <function get_asynclib at 0x7ff9a59cc1f0>

AttributeError: partially initialized module 'anyio._backends._asyncio' has no attribute 'run_sync_in_worker_thread' (most likely due to a circular import)

I'm not getting the same error each time. It's intermittent, sometimes I get an anyio error like this, sometimes I don't. Can't reproduce it on my local machine, only happening when deployed to uat / prod.

I've recently added import anyio._backends._asyncio to the top of myapp/web.py, currently monitoring to see if the anyio errors are gone, too early to say for sure at this stage.

Can anyone here confirm that the errors I'm seeing appear to be this race condition issue? Sure looks like it to me, as get_asynclib is at the bottom of both stack traces.

And can anyone suggest anything else I might want to do, to resolve the issue, other than chucking import anyio._backends._asyncio at the top of my app's bootstrap file that kicks off a background thread?

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

4 participants