使用超时在Python中停止函数

时间:2015-02-16 07:29:43

标签: django multithreading timeout uwsgi

我正在为我的网络服务编写健康检查端点。

如果组件正常工作,端点将调用一系列返回True的函数:

如果所有组件都正常工作,则认为系统正在运行:

def is_health():
    healthy = all(r for r in (database(), cache(), worker(), storage()))
    return healthy

当事情不起作用时,这些功能可能需要很长时间才能返回。例如,如果数据库因查询速度慢而陷入困境,database()可能需要30秒以上才能返回。

运行状况检查端点在Django视图的上下文中运行,在uWSGI容器内运行。如果请求/响应周期超过30秒,则该请求是harakiri-ed!

这是一个巨大的失败,因为我丢失了所有关于哪个组件花了很长时间记录的上下文信息。

我真正喜欢的是组件功能在超时或截止时间内运行:

with timeout(seconds=30):
    database_result = database()
    cache_result = cache()
    worker_result = worker()
    storage_result = storage()

在我的想象中,随着截止日期/ harakiri超时的临近,我可以中止剩余的健康检查,并只报告我完成的工作。

处理此类事情的正确方法是什么?

我查看了threading.ThreadQueue.Queue - 我的想法是创建工作和结果队列,然后使用线程来使用工作队列,同时将结果放在结果中队列。然后我可以使用线程的Thread.join函数来停止处理其余的组件。

我面临的一个挑战是,我不确定如何努力退出线程 - 如果它没有完成它的运行,我不会希望它永远存在。 / p>

这是我到目前为止的代码。我是在正确的轨道上吗?

import Queue
import threading
import time

class WorkThread(threading.Thread):
    def __init__(self, work_queue, result_queue):
        super(WorkThread, self).__init__()
        self.work_queue = work_queue
        self.result_queue = result_queue

        self._timeout = threading.Event()

    def timeout(self):
        self._timeout.set()

    def timed_out(self):
        return self._timeout.is_set()

    def run(self):
        while not self.timed_out():
            try:
                work_fn, work_arg = self.work_queue.get()
                retval = work_fn(work_arg)
                self.result_queue.put(retval)
            except (Queue.Empty):
                break

def work(retval, timeout=1):
    time.sleep(timeout)
    return retval

def main():
    # Two work items that will take at least two seconds to complete.
    work_queue = Queue.Queue()
    work_queue.put_nowait([work, 1])
    work_queue.put_nowait([work, 2])

    result_queue = Queue.Queue()

    # Run the `WorkThread`. It should complete one item from the work queue
    # before it times out.
    t = WorkThread(work_queue=work_queue, result_queue=result_queue)
    t.start()
    t.join(timeout=1.1)
    t.timeout()

    results = []
    while True:
        try:
            result = result_queue.get_nowait()
            results.append(result)
        except (Queue.Empty):
            break

    print results

if __name__ == "__main__":
    main()

更新

似乎在Python中你有一些选择可以实现这种性质的超时:

  1. 如果您完全控制了流程使用的信号,请使用SIGALARMS,但如果您在uWSGI这样的容器中运行,可能会出错。
  2. 线程,为您提供有限的超时控制。根据您的容器环境(如uWSGI),您可能需要设置选项以启用它们。
  3. 子进程,它为您提供完整的超时控制,但您需要了解它们如何改变您的服务消耗资源的方式。
  4. 使用现有网络超时。例如,如果您的健康检查的一部分是使用Celery工作人员,则可以依靠AsyncResult的{​​{1}}参数来约束执行。
  5. 什么都不做!定期登录。稍后分析。
  6. 我正在探索这些不同选择的好处。


    更新#2

    我整理了一个GitHub回购,其中包含有关该主题的更多信息:

    我有一天会把它输入答案,但TLDR就在这里:

0 个答案:

没有答案