uvicorn: uvicorn eats SIGINTs, does not propagate exceptions
The following snippet cannot be killed with a SIGINT (ctrl+c):
import asyncio
from starlette.applications import Starlette
from uvicorn import Config, Server
async def web_ui():
await Server(Config(Starlette())).serve()
async def task():
await asyncio.sleep(100000000000000)
async def main():
await asyncio.gather(web_ui(), task())
if __name__ == "__main__":
asyncio.run(main())
It appears that uvicorn is eating SIGINTs and does not propagate the KeyboardInterrupt and/or Cancelled exceptions. Thanks for having a look.
<picture> <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/encode/uvicorn/issues/1579/pledge.svg?darkmode=1">[!IMPORTANT]
- We’re using Polar.sh so you can upvote and help fund this issue.
- We receive the funding once the issue is completed & confirmed by you.
- Thank you in advance for helping prioritize & fund our backlog.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 11
- Comments: 18 (6 by maintainers)
This is not sufficient for an
asyncioapplication.KeyboardInterruptis needed to signal to the event loop to shut down all tasks, not just those manually tied to theuvicornserver; an application is impossible to shut down gracefully otherwise. It is also needed to signal to the orchestration of the program (be this a shell,systemd, or similar) that shutdown occurred properly.I’m not sure what kind of answer you are expecting to this. “run
uvicornas part of a largerasyncapplication” is a realistic use-case in my book. This requires being able to shut down the application using standard means; right nowuvicornprevents this (at least without extensive workarounds).In our specific case, we use
uvicornto implement an (optional) REST API of a highly concurrent resource management application. Mishandling SIGINT means that the application could not properly shut down and needed a hard kill, leaking other resources.You can just override install_signal_handlers function:
And then when you catch interrupt elsewhere you can just signal server to stop:
self.server.should_exit = TrueThis has turned out to be a real problem for running an optional
uvicornbased server inside an existingasyncioapplication. SinceServer.servefinishes gracefully and the original signal handlers are never restored there is no way to ^C the application itself.It looks okay’ish for
Server.serveto capture signals for graceful shutdown, but it should at least restore the original signal handlers and ideally also reproduce the original behaviour as well. From the looks of it,Server.serveshould raiseKeyboardInterruptwhen done and onlyServer.runshould suppress it.I don’t know if this could help but this is my workaround for my use case:
Nice! one more option is to override the
handle_exit:Hi all,
To chime in with another example.
I have a fairly trivial application where in a websocket handler, I’m using an async queue to push messages to clients. On shutdown, I’m using an event and a
Nonemessage to signal we are closing down. But the fastapi shutdown handler is never applied and I cannot trigger these sentinel messages. It’s ina deadlock position as far as I can tell.@Kludex Even when adding such code to manually handle the exit, the exit code is wrong for any naive handling. The program shown exits with
0; it should propagate the SIGINT if killed by that, though. A proper exit is viaKeyboardInterruptiff the application was killed by SIGINT.That needs quite some extra scaffolding to do right.
^ courtesy ping for @Kludex , who seems to be the most active maintainer
Well, for my problem I’ve uninstalled watchfiles so uvicorn falls back to polling which works.
Here’s a very hacky workaround