优雅地停止 Tornado ioLoop

Stop Gracefully Tornado ioLoop

我有使用 tornado 的 ioloop 的异步工作器功能。 我试图在 Ctrl+C 上优雅地关闭循环,但出现以下错误

tornado.ioloop.TimeoutError: Operation timed out after None seconds

我知道我可以抓住它,但我确实想以一种优雅的方式完成这个过程,我该如何实现?

#!/usr/bin/env python
import time
import signal
import random

from tornado import gen, ioloop, queues

concurrency = 10

def sig_exit(signum, frame):
    ioloop.IOLoop.current().add_callback_from_signal(shutdown)

def shutdown():
    print('Will shutdown in few seconds ...')
    io_loop = ioloop.IOLoop.current()

    deadline = time.time() + 3

    def stop_loop():
        now = time.time()
        if now < deadline and (io_loop._callbacks or io_loop._timeouts):
            io_loop.add_timeout(now + 1, stop_loop)
        else:
            io_loop.stop()
            print('Shutdown')

    stop_loop()

@gen.coroutine
def main():
    q = queues.Queue()
    q.put(1)

    @gen.coroutine
    def do_stuff():
        print("doing stuff")
        yield gen.Task(ioloop.IOLoop.instance().add_timeout, time.time() + random.randint(1, 5))
        print("done doing stuff")

    @gen.coroutine
    def worker():
        while True:
            yield do_stuff()

    for _ in range(concurrency):
        worker()

    yield q.join()


if __name__ == '__main__':
    signal.signal(signal.SIGTERM, sig_exit)
    signal.signal(signal.SIGINT, sig_exit)

    io_loop = ioloop.IOLoop.instance()
    io_loop.run_sync(main)

如果您使用的是 run_sync,您将无法再调用 IOLoop.stop - run_sync 现在对此负责。因此,如果您想关闭 "graceful"(而不是仅仅在您现在调用 stop() 时引发 KeyboardInterrupt 并退出堆栈跟踪),您需要更改传递给 run_sync 所以它退出。

一个可能的解决方案是 tornado.locks.Event:

# Create a global Event
shutdown_event = tornado.locks.Event()

def shutdown():
    # Same as in the question, but instead of `io_loop.stop()`:
    shutdown_event.set()

@gen.coroutine
def main():
    # Use a WaitIterator to exit when either the queue 
    # is done or shutdown is triggered. 
    wait_iter = gen.WaitIterator(q.join(), shutdown_event.wait())
    # In this case we just want to wait for the first one; we don't
    # need to actually iterate over the WaitIterator. 
    yield wait_iter.next()
async def main():
    tornado.options.parse_command_line()
    ...
    app = Application(db)
    app.listen(options.port)
        
    shutdown_event = tornado.locks.Event()
    def shutdown( signum, frame ):
        print("shutdown  database !!!!")
        db.close()
        shutdown_event.set()


    signal.signal(signal.SIGTERM, shutdown)
    signal.signal(signal.SIGINT, shutdown)

    await shutdown_event.wait()
    print("\n\nshutdown -h now")

if __name__ == "__main__":
    tornado.ioloop.IOLoop.current().run_sync(main)