adriangb / asapi

An opinionated set of utilities / patterns for FastAPI
17 stars 0 forks source link

Application startup fails #2

Open dineshbvadhia opened 3 months ago

dineshbvadhia commented 3 months ago

The code looks like:

import anyio
from asapi import serve
from fastapi import FastAPI

app = FastAPI()

app.include_router(router)
add_exception_handler(
    app,
    logger=logger,
    )

async def main():
    do_startup_stuff()    
    await serve(app, 8000)

if __name__ == "__main__":
    anyio.run(main)    

The code excecutes do_startup_stuff() successfully but fails to start the fastapi app. The error trace is:

INFO:     Started server process [19096]
INFO:     Waiting for application startup.
ERROR:    Traceback (most recent call last):
  File "...asapi\_signal_handling.py", line 46, in handle_signals
    yield stop
  File "...asapi\_serve.py", line 21, in serve
    await stop.wait()
  File "...anyio\_backends\_asyncio.py", line 1662, in wait
    await self._event.wait()
  File "... asyncio\locks.py", line 213, in wait
    await fut
asyncio.exceptions.CancelledError: Cancelled by cancel scope 1df6aaa8690

The full traceback is longer and can post if required.

adriangb commented 3 months ago

I think it will be. This seems to be something going on inside of do_startup_stuff(). This example works without issues:

import anyio
from asapi import serve
from fastapi import FastAPI

app = FastAPI()

def do_startup_stuff():
    print('Doing startup stuff')

async def main():
    do_startup_stuff()
    await serve(app, 8000)

if __name__ == '__main__':
    anyio.run(main)

I don't know what your do_startup_stuff() is or what add_exception_handler is

adriangb commented 3 months ago

Could you try removing one at a time of do_startup_stuff and add_exception_handler and mounting the router to see which one is causing the issue?

dineshbvadhia commented 3 months ago

At startup the following happens: cli validation, db connection creation and read data which work as expected. The db connection (dao) forms the fastapi dependency DaoDep = Annotated[Dao, Depends(resolve_dao)] which has been replaced with asapi Injected. The asapi skeleton code structure is:

from fastapi import FastAPI
import anyio
from asapi import Injected, serve, bind

class Dao:
    ...

@app.get("/")
async def get(dao: Injected[Dao]):
    ...

app = FastAPI()

async def main():
    do_cli_validation()     # works
    dao = Dao()
    bind(app, Dao, dao)
    do_read_data(dao)       # works, so dao = Dao() works
    await serve(app, 8000)

if __name__ == "__main__":
    anyio.run(main)  

The startup error is as before. Is the Injected dependency not configured correctly?

The full traceback is:

INFO:     Started server process [6840]
INFO:     Waiting for application startup.

ERROR:    Traceback (most recent call last):
  asapi\_signal_handling.py", line 46, in handle_signals
    yield stop
  asapi\_serve.py", line 21, in serve
    await stop.wait()
  anyio\_backends\_asyncio.py", line 1662, in wait
    await self._event.wait()
  asyncio\locks.py", line 213, in wait
    await fut
asyncio.exceptions.CancelledError: Cancelled by cancel scope 1d3f7af3a50

During handling of the above exception, another exception occurred:

  + Exception Group Traceback (most recent call last):
  |   anyio\_backends\_asyncio.py", line 2034, in run
  |     return runner.run(wrapper())
  |            ^^^^^^^^^^^^^^^^^^^^^
  |   asyncio\runners.py", line 118, in run
  |     return self._loop.run_until_complete(task)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   asyncio\base_events.py", line 653, in run_until_complete
  |     return future.result()
  |            ^^^^^^^^^^^^^^^
  |   anyio\_backends\_asyncio.py", line 2022, in wrapper
  |     return await func(*args)
  |            ^^^^^^^^^^^^^^^^^
  |   server_asapi.py", line 613, in main
  |     await serve(app, 8000)
  |   asapi\_serve.py", line 18, in serve
  |     async with handle_signals() as stop:
  |   contextlib.py", line 222, in __aexit__
  |     await self.gen.athrow(typ, value, traceback)
  |   asapi\_signal_handling.py", line 44, in handle_signals
  |     async with anyio.create_task_group() as tg:
  |   anyio\_backends\_asyncio.py", line 680, in __aexit__
  |     raise BaseExceptionGroup(
  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   asapi\_signal_handling.py", line 17, in _signal_handler
    |     with anyio.open_signal_receiver(signal.SIGTERM, signal.SIGINT) as signals:
    |   anyio\_backends\_asyncio.py", line 1805, in __enter__
    |     self._loop.add_signal_handler(sig, self._deliver, sig)
    |   asyncio\events.py", line 574, in add_signal_handler
    |     raise NotImplementedError
    | NotImplementedError
    +------------------------------------

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  starlette\routing.py", line 741, in lifespan
    await receive()
  uvicorn\lifespan\on.py", line 137, in receive
    return await self.receive_queue.get()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  asyncio\queues.py", line 158, in get
    await getter
asyncio.exceptions.CancelledError
dineshbvadhia commented 3 months ago

from asapi import Injected, serve, bind, validate_injections

Generates the error:

Traceback (most recent call last):
  File "...server_asapi.py", line 71, in <module>
    from asapi import Injected, serve, bind, validate_injections
ImportError: cannot import name 'validate_injections' from 'asapi' (...asapi\__init__.py)
adriangb commented 3 months ago

Do you mean that literally just the imports generate that error for you?

from asapi import Injected, serve, bind, validate_injections

?

That seems very strange: https://github.com/adriangb/asapi/blob/e2ed763efa7c8e0f6c5383575e036484159390e5/asapi/__init__.py#L1

Has been like that since the initial commit.

adriangb commented 3 months ago
|   asapi\_signal_handling.py", line 17, in _signal_handler
|     with anyio.open_signal_receiver(signal.SIGTERM, signal.SIGINT) as signals:
|   anyio\_backends\_asyncio.py", line 1805, in __enter__
|     self._loop.add_signal_handler(sig, self._deliver, sig)
|   asyncio\events.py", line 574, in add_signal_handler
|     raise NotImplementedError

That's a very weird place to have that error. What event loop implementation are you using? Can you share your version of Python, anyio and asapi?

Thanks

dineshbvadhia commented 3 months ago

Yes

from asapi import Injected, serve, bind, validate_injections

Generates the error:

Traceback (most recent call last):
  File "...server_asapi.py", line 71, in <module>
    from asapi import Injected, serve, bind, validate_injections
ImportError: cannot import name 'validate_injections' from 'asapi' (...asapi\__init__.py)

Also, happens when importing from python interpreter ie.

> python
Python 3.11.4 (tags/v3.11.4:d2340ef, Jun  7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import asapi
>>> from asapi import Injected, serve, bind, validate_injections
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'validate_injections' from 'asapi' (...asapi\__init__.py)
dineshbvadhia commented 3 months ago

Windows 11 Pro 23H2 w/ latest update python 3.11.4 anyio 4.4.0 asapi 0.1.2

adriangb commented 3 months ago

Ups I can reproduce! The wheel for asapi 0.1.2 was broken. I haven't really setup proper CI yet. Please try 0.1.3.

dineshbvadhia commented 3 months ago

from asapi import Injected, serve, bind, validate_injections

Solved with 0.1.3 :)

dineshbvadhia commented 3 months ago

But, the add_signal_handler (or other) remains ie.

Traceback (most recent call last):
  ...asapi\_signal_handling.py", line 48, in handle_signals
    yield stop
  ...asapi\_serve.py", line 21, in serve
    await stop.wait()
  ...anyio\_backends\_asyncio.py", line 1662, in wait
    await self._event.wait()
  ...asyncio\locks.py", line 213, in wait
    await fut
asyncio.exceptions.CancelledError: Cancelled by cancel scope 1ea4b2b9890

During handling of the above exception, another exception occurred:

  + Exception Group Traceback (most recent call last):
  |   ...server_asapi.py", line 585, in <module>
  |     anyio.run(main)
  |   ...anyio\_core\_eventloop.py", line 74, in run
  |     return async_backend.run(func, args, {}, backend_options)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   ...anyio\_backends\_asyncio.py", line 2034, in run
  |     return runner.run(wrapper())
  |            ^^^^^^^^^^^^^^^^^^^^^
  |   ...asyncio\runners.py", line 118, in run
  |     return self._loop.run_until_complete(task)
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   ...asyncio\base_events.py", line 653, in run_until_complete
  |     return future.result()
  |            ^^^^^^^^^^^^^^^
  |   ...anyio\_backends\_asyncio.py", line 2022, in wrapper
  |     return await func(*args)
  |            ^^^^^^^^^^^^^^^^^
  |   ...server_asapi.py", line 581, in main
  |     await serve(app, 8000)
  |   ...asapi\_serve.py", line 18, in serve
  |     async with handle_signals() as stop:
  |   ...contextlib.py", line 222, in __aexit__
  |     await self.gen.athrow(typ, value, traceback)
  |   ...asapi\_signal_handling.py", line 46, in handle_signals
  |     async with anyio.create_task_group() as tg:
  |   ...anyio\_backends\_asyncio.py", line 680, in __aexit__
  |     raise BaseExceptionGroup(
  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   ...asapi\_signal_handling.py", line 19, in _signal_handler
    |     with anyio.open_signal_receiver(signal.SIGTERM, signal.SIGINT) as signals:
    |   ...anyio\_backends\_asyncio.py", line 1805, in __enter__
    |     self._loop.add_signal_handler(sig, self._deliver, sig)
    |   ...asyncio\events.py", line 574, in add_signal_handler
    |     raise NotImplementedError
    | NotImplementedError
    +------------------------------------
adriangb commented 3 months ago

I feel like you might be hitting https://github.com/python/cpython/blob/1b0e63c81b54a937b089fe335761cba4a96c8cdf/Lib/asyncio/events.py#L578, which doesn't make much sense. I'll look deeper tomorrow hopefully.

dineshbvadhia commented 3 months ago

Thanks. In your time.

Fyi I'm testing asapi with my full system which works using the fastapi Annotated() dependency except for the cli problem which is related to uvicorn. Happy to be a guinea pig for asapi :)

adriangb commented 3 months ago

I now set up CI including Windows and added a test that passes.

I'm guessing you're hitting this line? https://github.com/python/cpython/blob/1b0e63c81b54a937b089fe335761cba4a96c8cdf/Lib/asyncio/events.py#L578 Could you share a couple lines above 574 and below in asyncio/events.py to confirm?

Also could you check if this snippet works for you?

import signal
import anyio

async def main() -> None:
    with anyio.open_signal_receiver(signal.SIGTERM, signal.SIGINT) as signals:
        async for sig in signals:
            print(f'Received signal {sig!r}')

anyio.run(main)

Thanks!

dineshbvadhia commented 3 months ago

asapi 0.1.3

The problem is at this line : https://github.com/python/cpython/blob/1b0e63c81b54a937b089fe335761cba4a96c8cdf/Lib/asyncio/events.py#L578

See the Traceback above https://github.com/adriangb/asapi/issues/2#issuecomment-2171690585

Don't understand what 'Could you share a couple lines above 574 and below in asyncio/events.py to confirm?' means?

The above snippet generated the Traceback:

Traceback (most recent call last):
  ...snippet.py", line 11, in <module>
    anyio.run(main)
  ...anyio\_core\_eventloop.py", line 74, in run
    return async_backend.run(func, args, {}, backend_options)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ...anyio\_backends\_asyncio.py", line 2034, in run
    return runner.run(wrapper())
           ^^^^^^^^^^^^^^^^^^^^^
  ...asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ...asyncio\base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  ...anyio\_backends\_asyncio.py", line 2022, in wrapper
    return await func(*args)
           ^^^^^^^^^^^^^^^^^
  ...snippet.py", line 6, in main
    with anyio.open_signal_receiver(signal.SIGTERM, signal.SIGINT) as signals:
  ...anyio\_backends\_asyncio.py", line 1805, in __enter__
    self._loop.add_signal_handler(sig, self._deliver, sig)
  ...asyncio\events.py", line 574, in add_signal_handler
    raise NotImplementedError
NotImplementedError
adriangb commented 3 months ago

I just wanted to confirm what line that error is coming from. Can you let me know what happens with the snippet in https://github.com/adriangb/asapi/issues/2#issuecomment-2181634297?

dineshbvadhia commented 3 months ago

The snippet:

import signal
import anyio

async def main() -> None:
    with anyio.open_signal_receiver(signal.SIGTERM, signal.SIGINT) as signals:
        async for sig in signals:
            print(f'Received signal {sig!r}')

anyio.run(main)

Generates the Traceback:

Traceback (most recent call last):
  ...snippet.py", line 11, in <module>
    anyio.run(main)
  ...anyio\_core\_eventloop.py", line 74, in run
    return async_backend.run(func, args, {}, backend_options)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ...anyio\_backends\_asyncio.py", line 2034, in run
    return runner.run(wrapper())
           ^^^^^^^^^^^^^^^^^^^^^
  ...asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ...asyncio\base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  ...anyio\_backends\_asyncio.py", line 2022, in wrapper
    return await func(*args)
           ^^^^^^^^^^^^^^^^^
  ...snippet.py", line 6, in main
    with anyio.open_signal_receiver(signal.SIGTERM, signal.SIGINT) as signals:
  ...anyio\_backends\_asyncio.py", line 1805, in __enter__
    self._loop.add_signal_handler(sig, self._deliver, sig)
  ...asyncio\events.py", line 574, in add_signal_handler
    raise NotImplementedError
NotImplementedError
adriangb commented 3 months ago

I think it may be that (some?) flavors of Windows don't support SIGTERM. Can you try removing SIGTERM?

Thank you for helping me debug, I don't have a Windows machine so I can't really test these things.

dineshbvadhia commented 3 months ago

Happy to help :)

Removing SIGTERM ie.

import signal
import anyio

async def main() -> None:
    with anyio.open_signal_receiver(signal.SIGINT) as signals:
        async for sig in signals:
            print(f'Received signal {sig!r}')

anyio.run(main)

Generates the Traceback:

Traceback (most recent call last):
  ...snippet.py", line 9, in <module>
    anyio.run(main)
  ...anyio\_core\_eventloop.py", line 74, in run
    return async_backend.run(func, args, {}, backend_options)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ...anyio\_backends\_asyncio.py", line 2034, in run
    return runner.run(wrapper())
           ^^^^^^^^^^^^^^^^^^^^^
  ...asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ...asyncio\base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  ...anyio\_backends\_asyncio.py", line 2022, in wrapper
    return await func(*args)
           ^^^^^^^^^^^^^^^^^
  ...snippet.py", line 5, in main
    with anyio.open_signal_receiver(signal.SIGINT) as signals:
  ...anyio\_backends\_asyncio.py", line 1805, in __enter__
    self._loop.add_signal_handler(sig, self._deliver, sig)
  ...asyncio\events.py", line 574, in add_signal_handler
    raise NotImplementedError
NotImplementedError
dineshbvadhia commented 3 months ago

At the anyio open_signal_receiverfunction https://github.com/agronholm/anyio/blob/e93d5c9157ad045f1f9dffbcbba6d005febdb9e9/src/anyio/_core/_signals.py#L10 it says:

warning:: Windows does not support signals natively so it is best to avoid
        relying on this in cross-platform applications.

Maybe setting the asyncio WindowsSelectorEventLoopPolicy is an option to try: https://docs.python.org/3.11/library/asyncio-policy.html#asyncio.WindowsSelectorEventLoopPolicy for Windows ('win*')

asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

adriangb commented 3 months ago

I’ll probably have to look at what Uvicorn does on Windows and just copy that.

On Sat, Jun 22, 2024 at 1:13 PM Henry Thornton @.***> wrote:

At the anyio open_signal_receiver function https://github.com/agronholm/anyio/blob/e93d5c9157ad045f1f9dffbcbba6d005febdb9e9/src/anyio/_core/_signals.py#L10 it says:

warning:: Windows does not support signals natively so it is best to avoid relying on this in cross-platform applications.

Maybe setting the asyncio WindowsSelectorEventLoopPolicy is an option to try: https://docs.python.org/3.11/library/asyncio-policy.html#asyncio.WindowsSelectorEventLoopPolicy for Windows ('win*')

asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

— Reply to this email directly, view it on GitHub https://github.com/adriangb/asapi/issues/2#issuecomment-2184136418, or unsubscribe https://github.com/notifications/unsubscribe-auth/AANMPP72P32BOUBRG6K66G3ZIW5FLAVCNFSM6AAAAABJISYRJKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCOBUGEZTMNBRHA . You are receiving this because you commented.Message ID: @.***>

dineshbvadhia commented 3 months ago

Saw this on the uvicorn site: https://github.com/encode/uvicorn/blob/d79f285184404694c77f7ca649858e7488270cf7/uvicorn/loops/asyncio.py

dineshbvadhia commented 2 months ago

Hi Wondering if you've had a chance to work on this? Thanks