部署于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
相关产品推荐
相关产品推荐

