【问题标题】:How to execute code on SIGTERM in Django request handler thread如何在 Django 请求处理程序线程中的 SIGTERM 上执行代码
【发布时间】:2021-08-18 12:19:34
【问题描述】:

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

背景资料

我有一个运行在 Docker 容器中的应用程序,其请求可能需要长时间运行。当 Docker 想要停止一个容器时,它首先发送一个 SIGTERM 信号,等待一段时间,然后发送一个 SIGKILL。通常,在 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)

并创建了一个捕获异常和基本异常的请求处理程序:

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”)。 完整代码可以在here找到。

我的问题

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

【问题讨论】:

    标签: python django sigterm


    【解决方案1】:

    好的,我做了一些调查,找到了我自己问题的答案。正如我在提出问题时有点怀疑的那样,对于这种问题,您可能希望对所要求的问题提供不同的解决方案。不过,我会在此处发布我的发现,因为如果将来有人发现该问题并发现他和/或她自己处于类似情况。

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

    接下来,Django 还为SIGTERM 信号注册了一个处理程序,参见the Django source code。因此,如果您向进程发送SIGTERM,这就是被触发的进程。我暂时在我的虚拟环境中评论了该行以进行更多调查,但当然这永远不会导致真正的解决方案。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-16
      • 2020-02-03
      • 1970-01-01
      • 2021-10-12
      • 1970-01-01
      • 2023-03-13
      • 2011-10-22
      • 1970-01-01
      相关资源
      最近更新 更多