You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

部署于IIS+wfastcgi的Django后台线程异常终止问题排查

Django长任务在IIS+wfastcgi环境下莫名终止的排查与解决

问题背景

我做了一个供4-5人使用的Django内部工具,通过页面Ajax触发一项耗时10-15分钟的长任务:首次Ajax调用会在数据库任务表创建条目,接着启动线程,通过Py4J调用Java应用,向遗留服务器发送命令并定期查询状态、更新任务表直到完成。

核心症状

在manage.py runserver调试环境下完全正常,但部署到IIS+wfastcgi后,任务能正常启动,但数分钟后会无预警停止,数据库任务表不再更新,且没有任何异常日志,像是被IIS或wfastcgi直接终止。


相关代码

核心业务代码

# 获取Py4J命令
def getMCLCmd():
    gateway = JavaGateway()
    try:
        cmd = gateway.entry_point.getMCLCmd()
        return cmd
    except Py4JNetworkError as e:
        # 可能Java服务未启动
        logger.info('Py4J未启动,正在启动')
        pyserver = py4JVM()
        pyserver.start()

    # 重试一次
    try:
        cmd = gateway.entry_point.getMCLCmd()
        return cmd
    except Py4JNetworkError as e:
        logger.debug('Py4J错误' + str(e))
        return None


class py4JVM(threading.Thread):
   def __init__(self):
        threading.Thread.__init__(self, daemon=True)
        self.error = None

   def run(self):
        try:
            process = subprocess.run(MCL_CMD, shell=True, check=True, stdout=subprocess.PIPE, universal_newlines=True)
            output = process.stdout
            logger.info(f'Py4J输出: {output}')
        except subprocess.CalledProcessError as e:
            self.error = f"命令执行出错(返回码 {e.returncode}): {e.output}"
            logger.error(self.error)


# 长任务线程类
class FirstInitThread(threading.Thread):
    def __init__(self, task: ClientTask):
        self.task = task
        threading.Thread.__init__(self)

    def run(self):
        cmd = getMCLCmd()
        if cmd is None:
            self.task.status = 0
            self.task.status_msg = '无法启动Py4J'
            self.task.save()
            logger.error('无法启动Py4J')
            return

        self.task.status = 1
        try:
            if cmd.sendCommand(self.task.config):
                while cmd.isRunning():
                    time.sleep(1)
                    msg = cmd.getNextMessage()
                    if msg:
                        logger.info(msg)
                        self.task.status_msg = msg
                        self.task.save()
                    else:
                        self.task.status_msg = self.task.status_msg + '.'
                        self.task.save()
                self.task.status = 2
                self.task.save()

        except Py4JJavaError as e:
            self.task.status = 2
            self.task.status_msg = '客户端错误'
            logger.error('客户端错误')
            self.task.save()

        except Exception as e:
            self.task.status = 2
            self.task.status_msg = '线程停止: ' + str(e)
            logger.error(self.task.status_msg )
            self.task.save()

任务启动视图

@csrf_exempt
def remoteConfig(request: HttpRequest):
    ... # 配置对象创建逻辑
    task = ClientTask()
    task.config = config
    task.status_msg = '初始化中'
    task.status = 0
    task.save()

    thread = FirstInitThread(task)
    thread.start()
    return HttpResponse(str(task.pk))

进度查询视图

@csrf_exempt
def initProgress(request: HttpRequest):
    if request.method != 'POST':
        return HttpResponse("无效请求")

    task_id = request.POST.get('task_id')
    task: Task = Task.objects.get(pk = task_id)
    if task is None:
        return HttpResponse('无效任务')
    
    if task.status == 2:
        return HttpResponse(f'已完成: {task.status_msg}')
    
    return HttpResponse(task.status_msg)

排查与解决办法

1. 调整IIS应用池超时配置

IIS应用池默认有闲置超时(20分钟)和请求超时(120秒),如果后续没有新请求触发,应用池会回收进程,直接终止后台线程:

  • 打开IIS管理器,找到对应应用池 → 高级设置
  • 把**闲置超时(分钟)**调到大于任务最大耗时(比如20分钟以上)
  • 把**请求超时(秒)**设为足够大的值(比如3600秒)
  • 关闭快速失败保护,避免临时错误导致进程回收
  • 取消固定时间回收,或者把回收时间设为非任务执行时段

2. 配置wfastcgi进程生命周期

wfastcgi受限于IIS的进程管理,自身也有超时逻辑,需要在web.config中添加配置:

<system.webServer>
  <handlers>
    <add name="Python FastCGI" path="*" verb="*" modules="FastCgiModule" scriptProcessor="C:\Python\python.exe|C:\Python\Lib\site-packages\wfastcgi.py" resourceType="Unspecified" requireAccess="Script" />
  </handlers>
  <fastCgi>
    <application fullPath="C:\Python\python.exe" arguments="C:\Python\Lib\site-packages\wfastcgi.py" maxInstances="1" instanceMaxRequests="10000" idleTimeout="3600" />
  </fastCgi>
</system.webServer>

其中idleTimeout设为大于任务耗时的秒数,maxInstances设为1避免多进程状态不一致。

3. 避免Web进程内启动后台线程

当前py4JVM线程设置了daemon=True,主进程被回收时会直接终止守护线程;即使FirstInitThread没设Daemon,主进程回收后它也会跟着停止。Web服务器的设计模式不适合直接托管长任务线程。

4. 改用异步任务队列(推荐)

最可靠的方式是把长任务剥离到独立worker进程,比如用Celery+Redis/RabbitMQ,或者轻量的django-q、django-background-tasks:

  • Web进程只负责提交任务和查询状态,不受IIS进程回收影响
  • 即使Web进程重启,worker仍会继续执行任务,状态更新不受中断

5. 补充日志排查

当前日志没捕获到IIS/wfastcgi的终止信号,可以做这些补充:

  • 在FirstInitThread的run方法开头加进程ID日志:logger.info(f'长任务启动,进程ID: {os.getpid()}')
  • 启用IIS的失败请求跟踪,捕获进程回收原因
  • 查看Windows事件查看器的应用程序日志,找IIS或wfastcgi的异常记录

内容的提问来源于stack exchange,提问作者langlais

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 22:54:54