question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Eager Asynchronous Construction of App Interferes with Running Event Loop

See original GitHub issue

Because Server#run calls Config#load within loop.run_until_complete(self.serve(...)), a config load that involves asynchronously constructing an app fails because the event loop is already running. Note that this config load will fail only if the app name is provided instead of the app itself. This is because in the latter case, the app had already been asynchronously constructed in its own run_until_complete prior to the run_until_complete launched by Server.

To reproduce

  1. pip install -e git+https://github.com/encode/uvicorn#egg=uvicorn
  2. Create main.py:
import asyncio

async def new_app():
    async def app(scope, receive, send):
        await send({
            'type': 'http.response.start',
            'status': 200,
            'headers': [
                [b'content-type', b'text/plain'],
            ],
        })
        await send({
            'type': 'http.response.body',
            'body': b'Hello, world!',
        })

    return app

app = asyncio.get_event_loop().run_until_complete(new_app())
  1. Run uvicorn main:app.

Expected behavior

INFO: Started server process [xxx]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

And that a server request would return Hello, world!

Actual behavior

Traceback (most recent call last):
  File "/home/mishac/dev/python/personal/async_init/venv/bin/uvicorn", line 33, in <module>
    sys.exit(load_entry_point('uvicorn', 'console_scripts', 'uvicorn')())
  File "/home/mishac/dev/python/personal/async_init/venv/lib/python3.8/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/home/mishac/dev/python/personal/async_init/venv/lib/python3.8/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/home/mishac/dev/python/personal/async_init/venv/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/mishac/dev/python/personal/async_init/venv/lib/python3.8/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/mishac/dev/python/personal/async_init/venv/src/uvicorn/uvicorn/main.py", line 362, in main
    run(**kwargs)
  File "/home/mishac/dev/python/personal/async_init/venv/src/uvicorn/uvicorn/main.py", line 386, in run
    server.run()
  File "/home/mishac/dev/python/personal/async_init/venv/src/uvicorn/uvicorn/server.py", line 49, in run
    loop.run_until_complete(self.serve(sockets=sockets))
  File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
  File "/home/mishac/dev/python/personal/async_init/venv/src/uvicorn/uvicorn/server.py", line 56, in serve
    config.load()
  File "/home/mishac/dev/python/personal/async_init/venv/src/uvicorn/uvicorn/config.py", line 308, in load
    self.loaded_app = import_from_string(self.app)
  File "/home/mishac/dev/python/personal/async_init/venv/src/uvicorn/uvicorn/importer.py", line 20, in import_from_string
    module = importlib.import_module(module_str)
  File "/usr/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 783, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "./main.py", line 19, in <module>
    app = asyncio.get_event_loop().run_until_complete(new_app())
  File "uvloop/loop.pyx", line 1450, in uvloop.loop.Loop.run_until_complete
  File "uvloop/loop.pyx", line 1443, in uvloop.loop.Loop.run_until_complete
  File "uvloop/loop.pyx", line 1351, in uvloop.loop.Loop.run_forever
  File "uvloop/loop.pyx", line 480, in uvloop.loop.Loop._run
RuntimeError: this event loop is already running.

Event loop started running before app could be asynchronously constructed.

Environment

Ubuntu 20.04 CPython 3.8.5 Uvicorn 0.13.3

Additional context

Replacing step 1 with pip install -e git+https://github.com/MarioIshac/uvicorn@feat-async-construction#egg=uvicorn will achieve the expected behavior.

The particular benefit I get from having the app asynchronously & eagerly constructed instead of lazily constructed within async def app(...) is that I can take advantage of gunicorn preloading. I can also have the server fail-fast instead of on-first-request in the case that the asynchronous construction fails (for example, database connection pool could not be created).

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:1
  • Comments:10 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
MarioIshaccommented, Jan 22, 2021

@florimondmanca Yup I looked over this section of Server, what my merge request did was change it into effectively:

# uvicorn code
loop = asyncio.new_event_loop()
app = loop.run_until_complete(getapp())

async def serve():  # uvicorn code
    await _mainloop(app)

loop.run_until_complete(serve())
loop.close()

What is the downside of such an approach? As far as I understand uvicorn is still controlling the event loop. Benefit of this approach is now I can advantage of preloading as shown in earlier reply. Whereas for things I want to control shutdown for and/or initialize per-process, I’d use ASGI lifespan.

0reactions
graingertcommented, Sep 28, 2021

https://bugs.python.org/issue21998 asyncio can’t survive a fork, you have to start a new loop and make sure the old loop has finished before forking - or use spawn

Read more comments on GitHub >

github_iconTop Results From Across the Web

Javascript: avoid eager async execution and prioritize calls on ...
What I would like to do is: Queue up all these async functions on the event loop, but prioritize UI events. If this...
Read more >
Event Loop — Python 3.11.1 documentation
The event loop is the core of every asyncio application. Event loops run asynchronous tasks and callbacks, perform network IO operations, and run...
Read more >
Intro to Async Concurrency in Python vs. Node.js - Medium
Async concurrency is the most practical way to do this and ... Actually it's more like “node has the event loop up and...
Read more >
How the heck does async/await work in Python 3.5?
Event loops provide a loop which lets you say, "when A happens then do B". Basically an event loop watches out for when...
Read more >
Chapter 1. Reactive Programming with RxJava - O'Reilly
First of all, concurrency can come from multiple places, not just threadpools. If the data source is already async because it is on...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found