目录
情况
当以debug(调试)方式运行时,退出时正常的。
当以daemon(守护进程)方式运行时,退出时会报错的。
这里已经把官方文档的几种常见情况都排除。头疼!!
机器信息
Linux *** 4.4.0-210-generic
Ubuntu 16.04.7 LTS \n \l
PHP 7.0.33-0ubuntu0.16.04.16 (cli) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.0.33-0ubuntu0.16.04.16, Copyright (c) 1999-2017, by Zend Technologies
stop faild
当停止时,workerman会提示 stop fail。
输出信息
Workerman[start.php] stop
Workerman[start.php] is stopping ...
Workerman[start.php] stop fail
进程情况
ps aux 如下
root 28549 0.0 1.9 312400 9628 ? Ss 17:50 0:00 WorkerMan: master process start_file=/home/php-socks5/start.php
root 28550 0.0 2.3 320876 11704 ? S 17:50 0:00 WorkerMan: master process start_file=/home/php-socks5/start.php
pstree -p 如下
after start
├─php(28549)───php(28550)─┬─php(28551)
│ └─php(28552)
after stop
├─php(28549)───php(28550)
也就是说 主进程退出失败,而且还是2个主进程。晕
strace -p
strace: Process 28549 attached
futex(0x7ff07be3c150, FUTEX_WAIT_PRIVATE, 0, NULL
strace: Process 28550 attached
futex(0x7ff07be3c150, FUTEX_WAIT_PRIVATE, 0, NULL
这个我看不明白。以后学习到了再补充
源码
关闭进程
打开文件 vendor/workerman/workerman/Worker.php
跳转到 981 行,可以看到以下内容,也就时关闭进程的逻辑。
先发送一个停止信号,再等待主进程退出。
case 'stop':
if ($mode === '-g') {
static::$_gracefulStop = true;
$sig = \SIGHUP;
static::log("Workerman[$start_file] is gracefully stopping ...");
} else {
static::$_gracefulStop = false;
$sig = \SIGINT;
static::log("Workerman[$start_file] is stopping ...");
}
$master_pid && \posix_kill($master_pid, $sig);
$timeout = 5;
$start_time = \time();
while (1) {
$master_is_alive = $master_pid && \posix_kill($master_pid, 0);
if ($master_is_alive) {
if (!static::$_gracefulStop && \time() - $start_time >= $timeout) {
static::log("Workerman[$start_file] stop fail");
exit;
}
\usleep(10000);
continue;
}
static::log("Workerman[$start_file] stop success");
if ($command === 'stop') {
exit(0);
}
if ($mode === '-d') {
static::$daemonize = true;
}
break;
}
那为啥会失败呢?而且有2个主进程,这个关闭的是那个呢?
守护进程启动
跳转到 1228 行,可以看到以下内容
protected static function daemonize()
{
if (!static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) {
return;
}
\umask(0);
$pid = \pcntl_fork();
if (-1 === $pid) {
throw new Exception('Fork fail');
} elseif ($pid > 0) {
exit(0);
}
if (-1 === \posix_setsid()) {
throw new Exception("Setsid fail");
}
$pid = \pcntl_fork();
if (-1 === $pid) {
throw new Exception("Fork fail");
} elseif (0 !== $pid) {
exit(0);
}
}
很明显,这里启动了守护进程,而且有2次fork。
这里也解答了刚刚的问题:
关闭的是哪个进程:
答案是 组长进程,但是为什么关不掉呢。因为它还开了一个进程。
#号内是原来备注。随后在workman 社区内找到
也就是说这里为了保险,fork2次。
尝试解决
注释大法。把第二个注释掉。测试。。。然并卵。
信号问题
随后继续测试和阅读源码,发下workerman是有安装了信号处理器,完全忽略了这个事情。
源码 Worker.php 532 行
public static function runAll()
{
static::checkSapiEnv();
static::init();
static::parseCommand();
static::daemonize();
static::initWorkers();
static::installSignal();
static::saveMasterPid();
static::displayUI();
static::forkWorkers();
static::resetStd();
static::monitorWorkers();
}
protected static function installSignal()
{
if (static::$_OS !== \OS_TYPE_LINUX) {
return;
}
$signalHandler = '\Workerman\Worker::signalHandler';
\pcntl_signal(\SIGINT, $signalHandler, false);
\pcntl_signal(\SIGTERM, $signalHandler, false);
\pcntl_signal(\SIGHUP, $signalHandler, false);
\pcntl_signal(\SIGUSR1, $signalHandler, false);
\pcntl_signal(\SIGQUIT, $signalHandler, false);
\pcntl_signal(\SIGUSR2, $signalHandler, false);
\pcntl_signal(\SIGIO, $signalHandler, false);
\pcntl_signal(\SIGPIPE, \SIG_IGN, false);
}
所以我们看看停止时脚本做了什么
stop 触发事件
public static function stopAll()
{
static::$_status = static::STATUS_SHUTDOWN;
if (static::$_masterPid === \posix_getpid()) {
static::log("Workerman[" . \basename(static::$_startFile) . "] stopping ...");
$worker_pid_array = static::getAllWorkerPids();
if (static::$_gracefulStop) {
$sig = \SIGHUP;
} else {
$sig = \SIGINT;
}
foreach ($worker_pid_array as $worker_pid) {
\posix_kill($worker_pid, $sig);
if(!static::$_gracefulStop){
Timer::add(static::KILL_WORKER_TIMER_TIME, '\posix_kill', array($worker_pid, \SIGKILL), false);
}
}
Timer::add(1, "\\Workerman\\Worker::checkIfChildRunning");
if (\is_file(static::$_statisticsFile)) {
@\unlink(static::$_statisticsFile);
}
}
else {
foreach (static::$_workers as $worker) {
if(!$worker->stopping){
$worker->stop();
$worker->stopping = true;
}
}
if (!static::$_gracefulStop || ConnectionInterface::$statistics['connection_count'] <= 0) {
static::$_workers = array();
if (static::$globalEvent) {
static::$globalEvent->destroy();
}
try {
exit(0);
} catch (Exception $e) {
}
}
}
}
最神奇的事情发生了
当我输入命令 stop 时,组长进程并没有触发 stopALl()。
其中:
信号安装成功, \pcntl_signal(\SIGINT, $signalHandler, false); 返回 1
关闭脚本信号发送成功 sig = \SIGINT; \posix_kill(master_pid, $sig); 返回 true
解决但没完全解决
解决退出问题
- 注释掉 fork agent
return;
$pid = \pcntl_fork();
if (-1 === $pid) {
throw new Exception("Fork fail");
} elseif (0 !== $pid) {
exit(0);
}
- \SIGINT 信号退出主进程失败时发送 \SIGKILL。
- 这样改完后,stop 和 restart 都可以正常运行。
if ($master_is_alive) {
if (!static::$_gracefulStop && \time() - $start_time >= $timeout) {
static::log("Workerman[$start_file] stop fail");
\posix_kill($master_pid,\SIGKILL);
}
\usleep(10000);
continue;
}
没完全解决
- 主进程不能退出,一直处于 “futex(0x7fbcf813f150, FUTEX_WAIT_PRIVATE, 0, NULL”状态。是退出失败卡在这还是卡在这导致退出失败,暂时没有弄明白
- stop -g 无法使用。也就是说workman不能平滑重启和关闭。
其他
官方文档
workerman 常见问题:停止失败