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

Python:仅向子进程发SIGTERM却终止父进程的问题排查

问题

我是Python信号处理新手,用FastAPI搭建了一个服务器。其中/run接口会启动一个耗时子进程任务,通过pathos.multiprocessing.ProcessingPool调用子进程的run_loop函数;/stop接口读取pid.txt中的子进程PID,发送SIGTERM信号终止任务。

我在子进程的run_loop函数中设置了SIGTERM信号处理器,触发时抛出SystemError,期望仅终止子进程并让父进程返回值114514,但实际触发/stop接口后,子进程虽按预期终止,整个FastAPI服务器(父进程)也随之停止。

主进程代码(test.py)

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pathos.multiprocessing import ProcessingPool
import os
import uvicorn
import signal
from sub import run_loop

app = FastAPI()

def run_py(**kwargs):
    i = kwargs['i']
    try:
        out = ProcessingPool().map(run, [[i]])[0]
    except SystemError as err:
        out = 114514

    return out

def run(args):
    re_fig = run_loop(args[0])
    return re_fig

@app.post("/run")
def run_main():
    i = 1
    out = run_py(i=i)
    return JSONResponse({"code": 200, "message": out})

@app.post("/stop")
def stop():
    with open("./pid.txt", "r") as f:
        pid = int(f.readline())
    os.kill(pid, signal.SIGTERM)

if __name__ == "__main__":
    uvicorn.run(app='test:app', host="0.0.0.0", port=1145, reload=False)

子进程代码(sub.py)

import signal
import os
import time

def loop(i):
    pid = os.getpid()
    with open("./pid.txt", "w") as f:
        f.write(str(pid))

    while i < 30:
        i += 1
        print(f"sleep {i}")
    return i

def run_loop(i):
    def sigterm_handler(_signo, _stack_frame):
        print("stop!!!!!!!!!!!!")
        raise SystemError

    signal.signal(signal.SIGTERM, sigterm_handler)

    i = loop(i)
    return i

终端输出

INFO:     Started server process [108395]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:1145 (Press CTRL+C to quit)
sleep 2
sleep 3
sleep 4
sleep 5
sleep 6
stop!!!!!!!!!!!!
INFO:     127.0.0.1:57480 - "POST /stop HTTP/1.1" 200 OK
INFO:     127.0.0.1:57476 - "POST /run HTTP/1.1" 200 OK
INFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [108395] 

如何实现仅终止子进程而不关闭整个服务器?


解决方案

问题根源

SystemError属于Python内置的严重异常类型,子进程抛出该异常后,会被ProcessingPool传递到主进程,触发Python默认的异常处理逻辑,进而导致FastAPI服务器终止。另外,主进程未对SIGTERM信号做防护,若信号误发送到主进程也会直接终止服务。

修改步骤

  1. 替换异常类型:用自定义异常替代SystemError,避免触发Python的严重异常处理流程
  2. 加固主进程信号防护:给主进程添加SIGTERM空处理器,防止误杀
  3. 优化异常捕获逻辑:确保父进程能精准捕获子进程的终止信号异常,返回指定值

修改后的代码

子进程代码(sub.py)

import signal
import os
import time

# 定义自定义终止异常
class TaskStoppedError(Exception):
    pass

def loop(i):
    pid = os.getpid()
    with open("./pid.txt", "w") as f:
        f.write(str(pid))

    while i < 30:
        i += 1
        print(f"sleep {i}")
        time.sleep(1)  # 增加睡眠,确保任务有足够时间被终止
    return i

def run_loop(i):
    def sigterm_handler(_signo, _stack_frame):
        print("stop!!!!!!!!!!!!")
        raise TaskStoppedError("任务被手动终止")

    signal.signal(signal.SIGTERM, sigterm_handler)

    try:
        return loop(i)
    except TaskStoppedError:
        # 重新抛出异常,确保父进程能捕获
        raise

主进程代码(test.py)

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pathos.multiprocessing import ProcessingPool
import os
import uvicorn
import signal
from sub import run_loop, TaskStoppedError

app = FastAPI()

def run_py(**kwargs):
    i = kwargs['i']
    try:
        out = ProcessingPool().map(run, [[i]])[0]
    except TaskStoppedError:
        out = 114514
    # 保留兜底异常捕获,处理其他意外情况
    except Exception:
        out = 114514

    return out

def run(args):
    return run_loop(args[0])

@app.post("/run")
def run_main():
    i = 1
    out = run_py(i=i)
    return JSONResponse({"code": 200, "message": out})

@app.post("/stop")
def stop():
    try:
        with open("./pid.txt", "r") as f:
            pid = int(f.readline())
        os.kill(pid, signal.SIGTERM)
        return JSONResponse({"code": 200, "message": "已发送终止信号"})
    except FileNotFoundError:
        return JSONResponse({"code": 400, "message": "未找到运行中的任务"})
    except ProcessLookupError:
        return JSONResponse({"code": 400, "message": "任务进程不存在"})

if __name__ == "__main__":
    # 给主进程添加SIGTERM空处理器,防止误触发终止
    def ignore_sigterm(_signo, _frame):
        pass
    signal.signal(signal.SIGTERM, ignore_sigterm)
    
    uvicorn.run(app='test:app', host="0.0.0.0", port=1145, reload=False)

效果说明

  • 子进程触发SIGTERM后抛出自定义异常,父进程捕获后返回114514,不会触发主进程的终止逻辑
  • 主进程的SIGTERM空处理器确保即使信号误发送到主进程,服务也不会停止
  • loop函数增加time.sleep(1),让任务有足够时间被终止,便于测试验证

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 10:07:34