-
-
Notifications
You must be signed in to change notification settings - Fork 745
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
Uvicorn cannot be shutdown programmatically #742
Comments
Hi, Not documented indeed, but a multithreaded approach should do… import contextlib
import time
import threading
import uvicorn
class Server(uvicorn.Server):
def install_signal_handlers(self):
pass
@contextlib.contextmanager
def run_in_thread(self):
thread = threading.Thread(target=self.run)
thread.start()
try:
while not self.started:
time.sleep(1e-3)
yield
finally:
self.should_exit = True
thread.join()
config = Config("example:app", host="127.0.0.1", port=5000, log_level="info")
server = Server(config=config)
with server.run_in_thread():
# Server started.
...
# Server stopped. Very handy to run a live test server locally using a pytest fixture… # conftest.py
import pytest
@pytest.fixture(scope="session")
def server():
server = ...
with server.run_in_thread():
yield |
I do appreciate the reply/code, but: Why is this not built in and documented? It's a webserver. Run_in_thread() and shutdown() are core functionality...am I wrong? |
I'm following @florimondmanca s approach to run pact tests on the provider side. However when using this i get the following error from uvicorn's Server.run method:
Any idea on that? Happens with python 3.6.9 as well as 3.7.7. another edit: I got it to work using
|
Fully agreed, this question of how to run and stop unicorn pops up on stack overflow as well, it's quite obscure as it is now |
I appreciate the feedback here, but then this brings the question — what would folks expect the usage API to be for something like this? I think a nice approach for allowing a programmatic shutdown of Uvicorn would be to exit the space of But then there's the question of A/ waiting for the server to startup (without having to reinvent the wheel each time), and B/ triggering the server shutdown and have it clean up gracefully (straight up cancelling a task is very... brutal). Personally, I'd love this kind of API: async with open_task_group() as tg:
tg.start_soon(server.serve)
await server.wait_started()
# ...
# Cause serve() to terminate soon, allowing the task
# to finish cleanly when exiting the context manager.
server.shutdown_soon(). Here @asynccontextmanager
async def start_concurrently(async_fn):
task = asyncio.create_task(async_fn())
try:
yield
finally:
await task It could be used like this: async with start_concurrently(server.serve):
await server.wait_started()
# ...
server.shutdown_soon() I'm also happy to discuss a threaded equivalent API, for use cases where an event loop isn't readily available or practical (for example, when testing the server using a sync HTTP client). Then we need to figure out how to make these APIs come to life. :-) Maybe this means we'll need sync/async equivalents, like |
That's quite an elaborate pondering, thank you for that! I'd be willing to use all these APIs:) Just for curiosityIf I understand correctly, having the
|
Well, yes, if we add a And then a Now that we know this is the kind of thing we want to have eventually, anyone that'd like to figure out internals and ways to implement this is very much welcome to. :) I'll reopen since I think this is indeed a valuable thing to add. |
For people that stop by here and want to try out florimondmanca's multithreaded approach and are a bit new to uvicorn, Config in the code comes from uvicorn. Configuration below also includes plazmakeks' addition in the case of running into a runtime error described by them.
Thanks for this discussion all. |
I also encountered this RuntimeError and I did not want to force I think it would help everyone if the following code was implemented for import asyncio
+ import threading
import uvloop
def uvloop_setup():
- asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
+ if (threading.current_thread() is threading.main_thread()):
+ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
+ else:
+ asyncio.set_event_loop(uvloop.new_event_loop()) |
stopping the event loop did not allow the server to clean up extraneous tasks that might still be dangling. see here: encode/uvicorn#742
stopping the event loop did not allow the server to clean up extraneous tasks that might still be dangling. see here: encode/uvicorn#742
@florimondmanca I used your code and found the code to be ever running in case self.started was never set. In my case , a port was already in use. But the program does not stop on exception here. Added some output Log below :-
And this line was never met. Hence I have modified the code as below . Since I am not familiar with the internals of uvicorn server, I may not know if the below code has some edge case or anything that I am missing. Let me know if I am missing anything !! class Server(uvicorn.Server):
def install_signal_handlers(self):
pass
@contextlib.contextmanager
def running(self):
thread = threading.Thread(target=self.run)
thread.start()
try:
while not self.started and thread.is_alive(): # Added a condition for checking if thread is alive
time.sleep(1e-3)
yield
finally:
self.should_exit = True
thread.join() |
I am very confused why this requires some crazy-hoops. I checked the def run(app, **kwargs):
runner = create_runner(app, kwargs)
runner.run()
def create_runner(app, **kwargs):
config = Config(app, **kwargs)
server = Server(config=config)
if (config.reload or config.workers > 1) and not isinstance(app, str):
logger = logging.getLogger("uvicorn.error")
logger.warning(
"You must pass the application as an import string to enable 'reload' or "
"'workers'."
)
sys.exit(1)
if config.should_reload:
sock = config.bind_socket()
supervisor = ChangeReload(config, target=server.run, sockets=[sock])
return supervisor
elif config.workers > 1:
sock = config.bind_socket()
supervisor = Multiprocess(config, target=server.run, sockets=[sock])
return supervisor
else:
return server This way someone can still do |
Anyone care to review #1011? It doesn't solve this issue completely, but it would make the solution a little simpler. |
I have not had success in triggering this to shut down from within a FastAPI route. Could anyone shed light as to how this can be made to work without resorting to psutil process-killing?
|
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
There is no documented way to shutdown uvicorn in python:
ex:
How do we shutdown uvicorn?
The text was updated successfully, but these errors were encountered: