如何在Django请求处理程序线程中的SIGTERM上执行代码



我目前正在尝试理解Django中接收SIGTERM时的信号处理。

背景信息

我有一个在Docker容器中运行的应用程序,可能会有长时间的运行请求。当Docker想要停止一个容器时,它首先发送一个SIGTERM信号,等待一段时间,然后发送一个SIG KILL。通常,在SIGTERM上,您会停止接收新的请求,并希望当前运行的请求在Docker决定发送SIGKILL之前完成。然而,在我的应用程序中,我想保存已经尝试过的请求,并发现这比现在完成请求更重要。因此,我更希望当前的请求在SIGTERM上关闭,这样我就可以优雅地结束它们(并保存它们的状态(,而不是等待SIGKILL。

我的尝试

我的理论是,您可以为SIGTERM注册一个信号侦听器,该侦听器执行sys.exit(),从而引发SystemExit异常。然后,我想在请求处理程序中捕获该异常,并保存我的状态。作为第一个实验,我为Django开发服务器创建了一个模拟项目。我在Appconfig.ready()函数中注册了信号:

import signal
import sys
from django.apps import AppConfig
import logging
logger = logging.getLogger(__name__)
def signal_handler(signal_num, frame):
sys.exit()
class TesterConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'tester'
def ready(self):
logger.info('starting ready')
signal.signal(signal.SIGTERM, signal_handler)

并创建了一个捕获Exceptions和BaseExceptions的请求处理程序:

import logging
import sys
import time
from django.http import HttpResponse
def handler(request):
try:
logger.info('start')
while True:
time.sleep(1)
except Exception:
logger.info('exception')
except BaseException:
logger.info('baseexception')
return HttpResponse('hallo')

但是,当我启动开发服务器用户python manage.py runserver,然后使用kill -n 15 <pid>发送终止信号时,不会记录"baseexception"消息("start"确实会记录(。完整的代码可以在这里找到。

我的问题

我的假设是SIGTERM信号是在主线程中处理的,所以sys.exit()调用是在主螺纹中处理的。因此,在运行请求处理程序的线程中不会引发异常,也不会捕获任何内容。如何更改代码以在请求处理程序线程中引发SystemError?我需要该线程的一些信息来记录,所以我不能直接在信号处理程序中"记录"一些东西。

好的,我做了一些调查,找到了我自己问题的答案。正如我在提出这个问题时有点怀疑的那样,这是一种你可能想要与被要求的解决方案不同的解决方案的问题。然而,我会在这里发布我的发现,因为如果将来有人发现这个问题,并发现他和/或她自己也处于类似的情况。

有几个原因导致上述方法不起作用。首先,我忘记在INSTALLED_APPS中注册我的应用程序,所以TesterConfig.ready中的代码实际上没有执行。

接下来,Django还为SIGTERM信号注册了一个处理程序,请参阅Django源代码。因此,如果您向进程发送一个SIGTERM,就会触发它。我暂时在我的虚拟环境中评论了这句话,以进行更多的调查,但当然,这永远不会带来真正的解决方案。

sys.exit()函数确实引发了SystemExit异常,但该异常仅在线程本身中处理。如果您希望在线程之间进行通信,您可能希望使用Event并在要执行的线程中定期检查它。

如果您正在寻找在通过gunicorn运行Django时如何做到这一点的建议,我发现如果您使用sync工作程序,您可以在views.py中注册信号,因为请求将在主线程中处理。最后,我在这里注册了信号,并编写了一条日志行,在信号处理程序中引发了一个Exception。然后,这将由已经到位的异常处理来处理。

最新更新