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

PHP执行shell脚本时如何获取运行中PID并终止对应进程

问题根源

你现在的取消请求不生效,是三个问题叠加导致的:

  • 核心阻塞原因是PHP的Session独占锁机制:处理Run按钮请求的PHP脚本,从调用session_start()开始就会给Session文件加独占锁,在脚本执行结束/主动释放锁之前,其他所有需要使用同一个Session的请求(包括你发的Cancel Ajax请求)都会被阻塞排队,必须等Run的请求跑完释放锁,Cancel请求才能被处理,这时候进程早就执行完了。
  • 后台进程启动方式错误:你当前的exec写法没有把脚本的标准输出、错误输出完全重定向脱离PHP进程,PHP会一直等待管道输出读取完成才会继续往下执行,根本不会立刻返回PID,也不会执行到后续释放锁的逻辑。
  • 取消接口存在逻辑错误:cancelEx的case分支没有加break,就算请求进来也会走到default分支返回错误;另外用ps命令匹配进程名找PID的方式可靠性极差,很容易误杀其他同名进程。
修复方案

1. 修正Run按钮的进程启动逻辑

启动进程时第一时间拿到PID、写入Session后立刻释放Session锁,同时正确配置后台进程的输出重定向,让PHP不用等脚本执行完就可以返回响应:

if( isset($_POST["runBtn"]) ){
    session_start();
    // 直接通过脚本路径执行,不要读取脚本内容拼接到命令里,避免特殊字符解析错误
    $scriptPath = __DIR__ . '/runScript.sh';
    $logPath = __DIR__ . '/runtime/run_exec.log';
    // 确保日志目录存在且有写入权限
    if(!is_dir(dirname($logPath))) mkdir(dirname($logPath), 0755, true);
    // 正确后台启动命令:nohup挂起+所有输出重定向到日志文件,执行后立刻返回PID
    $cmd = sprintf(
        'nohup bash %s > %s 2>&1 & echo $!',
        escapeshellarg($scriptPath),
        escapeshellarg($logPath)
    );
    exec($cmd, $output);
    $pid = (int)$output[0];
    $_SESSION["runPID"] = $pid;
    // 立刻写入Session数据并释放锁,不阻塞后续其他请求
    session_write_close();
    // 如果你用的是PHP-FPM,加这行可以直接把响应返回给前端,彻底不阻塞
    if(function_exists('fastcgi_finish_request')) fastcgi_finish_request();

    // 这里直接返回启动成功响应即可,不需要等脚本跑完
    echo json_encode(['code' => 0, 'msg' => '实验已启动', 'pid' => $pid]);
    exit;
}

2. 修正Cancel按钮的后端接口逻辑

去掉无效的script输出,直接读取Session中存储的PID杀进程,补全缺失的break,同时杀掉整个进程组避免残留子进程:

<?php
header('Content-Type: application/json');
$aResult = array();
session_start();

if( !isset($_POST['functionname']) ) { 
    $aResult['error'] = 'No function name!'; 
}

if( !isset($aResult['error']) ) {
    switch($_POST['functionname']) {
        case 'cancelEx':
            $pid = $_SESSION['runPID'] ?? 0;
            if($pid > 0){
                // 注意PID前的负号:杀掉整个进程组,避免脚本启动的子进程残留
                shell_exec(sprintf('kill -9 -%d 2>&1', $pid));
                unset($_SESSION['runPID']);
                $aResult['result'] = '实验已成功终止';
            }else{
                $aResult['error'] = '当前没有运行中的实验';
            }
            break; // 必须加break,否则会走到default分支报错
        default:
            $aResult['error'] = 'Not found function '.$_POST['functionname'].'!';
            break;
    }
}

session_write_close();
echo json_encode($aResult);
?>
注意事项
  • 提前创建日志存储目录,确保PHP运行用户对该目录有读写权限,否则输出重定向失败会导致exec再次阻塞。
  • 不要在返回JSON的接口中输出<script>等HTML内容,会导致前端JSON解析失败,拿不到接口返回结果。
  • 如果你的脚本需要长时间运行,建议修改php.ini中max_execution_time配置,或者在脚本开头加set_time_limit(0),避免PHP超时主动终止进程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 14:06:30