多进程信号处理相关:父进程的第一代子进程可以接收到SIGTERM信号,而第二代以后的子进程则无法接收到SIGTERM信号
总结
问题已经得到解决(再次非常感谢),总结原因是:二代子进程(及三代以后)如果在信号处理内启动的话就接收不到SIGTERM信号了。整理最后答主的思想,最后解决问题的代码示例如:
$pid = pcntl_fork();
if ($pid < 0) {
exit('fork失败');
} elseif($pid > 0) {
exit('父进程退出');
}
// 升级为守护进程
if (!posix_setsid()) {
exit('setsid error');
}
$childs = []; //管理子进程
for($i=1; $i<3; $i++) {
fork();
}
function mylog($msg) {
$file = __DIR__ . '/test.log';
if (!is_file($file)) {
@touch($file);
}
@error_log($msg . PHP_EOL, 3, $file);
}
function worker($i) {
while(1) {
// 可能是一个循环体任务,如AMQP的消费者
}
}
function fork() {
global $childs;
$pid = pcntl_fork();
if ($pid < 0) {
exit('fork失败');
} elseif($pid > 0) {
$childs[] = $pid;
} else {
mylog('pid :'. posix_getpid());
worker(1);
}
}
// 子进程reload都放在这,而不是从信号处理内执行
while(true) {
pcntl_signal_dispatch();
foreach($childs as $k=>$pid) {
$result = pcntl_waitpid($pid, $status, WNOHANG);
if ($result == $pid || $result == -1) {
unset($childs[$k]);
fork();
}
}
if (empty($childs)) break;
sleep(1);
}
—————————分割线 ————————–
问题描述
父进程的第二代子进程执行sudo kill -15 第二代子进程
时,父进程无法接收到SIGTERM信号。
期望
希望得到大家的指点!
描述
初衷:模拟Supervisor实现子进程的reload和stop功能
父进程接收子进程退出的信号,用SIGCHLP捕捉并处理reload或者stop。如下两个子进程的信号将会被父进程接收到:
- SIGTERM(15):实现reload功能
- SIGSTOP(9):实现stop功能
实现过程:父进程启动之后称为守护进程,并产生第一代子进程,然后子进程通过 sudo kill -15 第一代子进程
之后父进程成功捕获到SIGTERM信号并且在信号处理业务中重启一个子进程(第二代子进程),然后在执行 sudo kill -15 第二代子进程
,父进程就无法接收到SIGTERM信号了。
如下是我自己测试这个问题的日志记录:
接下来执行sudo kill -15 32946
,并且得到截图如:
然后在shell窗口中执行sudo kill -15 33217
,如截图:
可见第二次执行的时候子进程ID为33217的并没退出也没重启。
DEMO
@JaguarJack
demo写的比较仓促,好歹能够复现,再次非常感谢!
步骤
(1)执行 php SignoDemo.php
(2)另起一个窗口:ps -ef | grep Signo,可以看到2个第一代子进程
(3)sudo kill -15 pid(第一代子进程PID),发现重启了一个子进程(二代子进程)
(4)sudo kill -15 pid(第二代子进程PID),发现这个二代子进程不在退出。
(5)可以观看执行demo的目录下有没有生成一个test.log,可以监听到主进程捕获到的信号信息。
(6)最后别忘了执行:ps -ef | grep Signo | grep -v grep | awk '{print "sudo kill -9 " $2}' |sh
代码
<?php
$pid = pcntl_fork();
if ($pid < 0) {
exit('fork失败');
} elseif($pid > 0) {
exit('父进程退出');
}
// 升级为守护进程
if (!posix_setsid()) {
exit('setsid error');
}
@cli_set_process_title('Master Process');
$childs = []; //管理子进程
$end = false; //控制父进程退出
// 父进程捕获子进程退出状态
pcntl_signal(SIGCHLD, function($signo, $siginfo) {
global $childs;
global $end;
$sigStatus = @$siginfo['status'];
$sigPid = @$siginfo['pid'];
// 这里第一代子进程可以接收到信号,第二代就接收不到了!
mylog("status:{$sigStatus}, pid:{$sigPid}");
foreach($childs as $k=>$pid) {
$result = pcntl_waitpid($pid, $status, WNOHANG);
if ($result == $pid || $result == -1) {
// 回收子进程
unset($childs[$k]);
if ($sigStatus == SIGKILL) continue;
// 这里是我实现reload的地方
if ($sigStatus == SIGTERM) {
forkCallback(function() {
worker('reload');
});
}
}
}
if (empty($childs)) $end = true;
});
for($i=1; $i<3; $i++) {
forkCallback(function() use ($i) {
worker($i);
});
}
function mylog($msg) {
$file = __DIR__ . '/test.log';
if (!is_file($file)) {
@touch($file);
}
@error_log($msg . PHP_EOL, 3, $file);
}
function worker($i) {
while(1) {
echo "worker: {$i} \n";
sleep(2);
}
}
function forkCallback($callback) {
global $childs;
$pid = pcntl_fork();
if ($pid < 0) {
exit('fork失败');
} elseif($pid > 0) {
$childs[] = $pid;
} else {
@cli_set_process_title('work process');
$callback();
exit;
}
}
while(!$end) {
pcntl_signal_dispatch();
sleep(1);
}
调试后发现可能是这里出现了
二次发送 SIGTERM 信号似乎被忽略,具体什么情况我没有去寻找。需要 trace 追踪下。 这段代码信号监听被删除掉后,改成
应该就可以了,全部代码私信给你了。你这种写法导致了问题出现,具体不详