Are there any better ways to run uvicorn in thread?

Uvicorn will not run inside thread because signals don't work in threads. Just removing the signal handling stops server from closing (needs to be forcefully shut down)

My solution was interferring with the __new__ function to get the server object and creating a shutdown function, and then binding that to a signal outside the thread.

However this is a really ugly solution. Are there better ways?

def run():
    '''
    Start uvicorn server
    returns exit function
    '''
    server = None

    old_new = uvicorn.Server.__new__

    def spoof_server(self, *_, **__):
        '''Interfeer with __new__ to set server'''
        nonlocal server
        server = old_new(self)
        return server

    uvicorn.Server.__new__ = spoof_server
    uvicorn.Server.install_signal_handlers = lambda *_, **__: None

    Thread(target=uvicorn.run, args=[make_app()]).start()

    def exit_server():
        print('exiting...')
        server.handle_exit(None, None)

    return exit_server

Upvotes: 1

Views: 7537

Answers (1)

sherlock_holmes
sherlock_holmes

Reputation: 21

I was looking for something like this as well. I found this answer that helped me. https://stackoverflow.com/a/64521239/13029591

I'll post the snippet here:

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 is started.
    ...
    # Server will be stopped once code put here is completed
    ...

# Server stopped.

Upvotes: 2

Related Questions