我想根据他们的描述here来处理Heroku上的dyno重启:
在此期间,他们应停止接受新请求或作业,并尝试完成当前请求,或将作业放回队列以供其他工作进程处理。
从外观上看,当python收到SIGTERM并调用信号处理程序时(per signal.signal
),当前运行的线程停止,因此请求在运行过程中停止。
我如何满足这两个要求? (停止接受新请求+完成当前请求)
答案 0 :(得分:2)
编辑:添加了简化的示例代码,更好地解释了正在进行的请求/终止,并添加了CrazyPython中的要点。
从表面上看,你有4个问题需要解决。我将轮流接受它们,然后提供一些示例代码,以帮助澄清:
处理SIGTERM
这很简单。您只需设置一个信号处理程序即可注意到您需要关闭。 PMOTW有很多关于如何捕捉信号的例子。您可以使用此代码的变体来捕获SIGTERM并设置一个表示您正在关闭的全局标志。
拒绝新请求
Django middleware提供了一种将任何HTTP请求挂钩到应用程序的简洁方法。您可以创建一个简单的process_request()
挂钩,如果设置了全局标志(从上面),则会返回错误页面。
完成现有请求
停止任何新请求后,您现在必须完成当前请求。虽然你现在可能不相信,但这意味着你什么都不做,让程序在SIGTERM之后继续像往常一样运行。让我扩展一下......
与heroku的合同是你必须在SIGTERM的10s内完成,否则它将发送SIGKILL。这意味着您无法做任何事情(作为一个表现良好的应用程序)来确保所有请求始终完成。考虑以下两种情况:
因此,在这两种情况下,解决方案只是让程序继续运行以在终止之前完成尽可能多的当前请求。
终止您的申请
最简单的事情可能是等待SIGKILL在10秒后从heroku出现。它并不优雅,但应该没问题,因为你拒绝任何新的请求。
如果这还不够好,您需要跟踪未完成的请求并使用它来决定何时可以关闭您的应用程序。关闭您的应用程序的确切方式将取决于托管它的任何内容,因此我无法在那里为您提供准确的指导。希望示例代码为您提供足够的指针。
示例代码
从PMOTW中的信号处理程序示例开始,我已经加强了代码以添加多个线程处理请求和终止管理器以捕获信号并允许应用程序正常关闭。您应该能够在Python2.7中运行它,然后尝试终止该过程。
在此示例的基础上,CrazyPython创建了此gist以在django中提供具体实现。
import signal
import os
import time
import threading
import random
class TerminationManager(object):
def __init__(self):
self._running = True
self._requests = 0
self._lock = threading.Lock()
signal.signal(signal.SIGTERM, self._start_shutdown)
def _start_shutdown(self, signum, stack):
print 'Received:', signum
self._running = False
def start_request(self):
with self._lock:
self._requests += 1
def stop_request(self):
with self._lock:
self._requests -= 1
def is_running(self):
return self._running or self._requests > 0
def running_requests(self):
return self._requests
class DummyWorker(threading.Thread):
def __init__(self, app_manager):
super(DummyWorker, self).__init__()
self._manager = app_manager
def run(self):
while self._manager.is_running():
# Emulate random work and delay between requests.
if random.random() > 0.9:
self._manager.start_request()
time.sleep(random.randint(1, 3))
self._manager.stop_request()
else:
time.sleep(1)
print "Stopping worker"
manager = TerminationManager()
print 'My PID is:', os.getpid()
for _ in xrange(10):
t = DummyWorker(manager)
t.start()
while manager.is_running():
print 'Waiting with {} running requests'.format(manager.running_requests())
time.sleep(5)
print 'All done!'