问题
Okay, this is very weird, but here goes -
import asyncio
from starlette.applications import Starlette
class MyTasks:
def __init__(self):
self.task = None
async def main(self):
self.task = asyncio.create_task(self.hello())
async def hello(self):
raise ValueError
async def main():
await MyTasks().main()
app = Starlette(on_startup=[main])
$ uvicorn test:app
INFO: Started server process [26622]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Hmm, no ValueError
here...
Now, remove the assignment to self.task
in MyTasks.main()
.
async def main(self):
asyncio.create_task(self.hello())
...
$ uvicorn test:app
INFO: Started server process [29083]
INFO: Waiting for application startup.
ERROR: Task exception was never retrieved
future: <Task finished name='Task-3' coro=<MyTasks.hello() done, defined at ./test.py:13> exception=ValueError()>
Traceback (most recent call last):
File "./test.py", line 14, in hello
raise ValueError
ValueError
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
... And voila.
What's going on here? How does that assignment make or break the exception logging!?
回答1:
A task is a subclass of future, which means that it has the concept of a result. In case of a task, the result is the value returned by the coroutine it drives. If the coroutine raises an exception, then the exception is encapsulated in the task object. Correctly written code is expected to eventually either await the task or access its result, in order for exceptions not to pass silently.
To help debug code that forgets to access the task, task's destructor logs an error if the task raised an exception but was never awaited. This error cannot be logged before running the destructor because as long as your code holds on to the task object, it might conceivable await it at any point. The point when the destructor runs is the first instance when Python can reliably "prove" that the task is unawaited.
But this last-ditch logging is not something you should rely on, it's provided on a best-effort basis. For example, running the destructor can be postponed due to GC. I expect that assigning the task to an instance whose bound method is being driven by the task makes the task part of a reference cycle. This postpones running the destructor until a full GC, and you don't see the log.
To fix the problem, you should either catch the exceptions in your coroutine and log them yourself instead of allowing them to propagate, or actually await the task at some point in your code.
回答2:
Sorry, this was a dupe of this other question.
A drop-in replacement to asyncio.create_task()
has been posted there.
Here's the output, after using the replacement create_task()
Case 1: With await self.task
$ uvicorn test:app
INFO: Started server process [33213]
INFO: Waiting for application startup.
ERROR: Traceback (most recent call last):
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/starlette/routing.py", line 517, in lifespan
await self.startup()
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/starlette/routing.py", line 494, in startup
await handler()
File "./test.py", line 35, in main
await MyTasks().main()
File "./test.py", line 27, in main
print(await self.task)
File "./test.py", line 13, in wrapper
return await task
File "./test.py", line 30, in hello
raise ValueError
ValueError
ERROR: Application startup failed. Exiting.
Case 2: Without await self.task
$ uvicorn test:app
INFO: Started server process [32627]
INFO: Waiting for application startup.
INFO: Application startup complete.
ERROR: Exception in callback <function create_task.<locals>.on_done at 0x10c519550>
handle: <Handle create_task.<locals>.on_done created at /Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py:382>
source_traceback: Object created at (most recent call last):
File "/Users/dev/.virtualenvs/server-99338def/bin/uvicorn", line 8, in <module>
sys.exit(main())
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 764, in __call__
return self.main(*args, **kwargs)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 717, in main
rv = self.invoke(ctx)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 956, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/click/core.py", line 555, in invoke
return callback(*args, **kwargs)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py", line 331, in main
run(**kwargs)
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py", line 354, in run
server.run()
File "/Users/dev/.virtualenvs/server-99338def/lib/python3.8/site-packages/uvicorn/main.py", line 382, in run
loop.run_until_complete(self.serve(sockets=sockets))
Traceback (most recent call last):
File "uvloop/cbhandles.pyx", line 70, in uvloop.loop.Handle._run
File "./test.py", line 9, in on_done
fut.result()
File "./test.py", line 30, in hello
raise ValueError
ValueError
来源:https://stackoverflow.com/questions/60287285/starlette-asyncio-create-task-doesnt-log-error-if-task-object-is-stored-in