不调用进程信号处理程序
Process signal handlers are not called
我正在开发 pre-forking TCP 套接字服务器,用 PHP 编写。
守护进程(parent 进程)派生一些 children 然后等待直到它被告知退出并且 children 全部消失或者它收到一个信号。
SIGINT 和 SIGTERM 导致它向所有 children 发送 SIGTERM。
child人设置了自己的信号处理程序:SIGTERM 导致干净退出。 SIGUSR1 导致它转储一些状态信息(只是在下面的示例代码中打印它收到信号)。
如果 child 意外退出,parent 将启动一个新的 child,除非退出标志已由 SIGINT 处理程序设置。
最初的 children,在守护程序初始化期间分叉,按预期对信号做出反应。
新分叉 children 以替换意外的 child 退出,不 响应信号。
下面的代码可以用来演示这一点:
<?php
$children = [];
$exiting = false;
pcntl_async_signals( true );
pcntl_signal( SIGCHLD, 'sigchldHandler' );
pcntl_signal( SIGINT, 'sigintHandler' );
pcntl_signal( SIGTERM, 'sigintHandler' );
// Fork our children.
for( $ii = 0; $ii < 1; $ii++ )
{
startChild();
}
// Forks a single child.
function startChild()
{
global $children;
echo "Parent: starting child\n";
$pid = pcntl_fork();
switch( true )
{
case ( $pid > 0 ):
$children[$pid] = $pid;
break;
case ( $pid === 0 ):
child();
exit( 0 );
default:
die( 'Parent: pcntl_fork() failed' );
break;
}
}
// As long as we have any children...
while( true )
{
if( empty( $children ) ) break;
sleep( 1 );
}
// The child process.
function child()
{
$pid = posix_getpid();
echo "Child $pid: started\n";
sleep( 10 ); // Give us a chance to start strace (4/30/19 08:27)
pcntl_sigprocmask( SIG_SETMASK, [] ); // Make sure nothing is blocked.
pcntl_async_signals( true ); // This may be inherited.
pcntl_signal( SIGINT, SIG_IGN ); // Ignore SIGINT.
pcntl_signal( SIGTERM, function() use ( $pid ) // Exit on SIGTERM.
{
echo "Child $pid: received SIGTERM\n";
exit( 0 );
}, false );
pcntl_signal( SIGUSR1, function() use( $pid ) // Acknowledge SIGUSR1.
{
printf( "Child %d: Received SIGUSR1\n", $pid );
});
// Do "work" here.
while( true )
{
sleep( 60 );
}
}
// Handle SIGCHLD in the parent.
// Start a new child unless we're exiting.
function sigchldHandler()
{
global $children, $exiting;
echo "Parent: received SIGCHLD\n";
while( true )
{
if( ( $pid = pcntl_wait( $status, WNOHANG ) ) < 1 )
{
break;
}
echo "Parent: child $pid exited\n";
unset( $children[$pid] );
if( !$exiting )
{
startChild();
}
}
}
// Handle SIGINT in the parent.
// Set exiting to true and send SIGTERM to all children.
function sigintHandler()
{
global $children, $exiting;
$exiting = true;
echo PHP_EOL;
foreach( $children as $pid )
{
echo "Parent: sending SIGTERM to $pid\n";
posix_kill( $pid, SIGTERM );
}
}
运行 这个脚本在终端会话中。初始输出将与此类似,但 PID 不同:
Parent: starting child
Child 65016: started
从不同的终端会话发出终止命令:
# kill -USR1 65016
child 进程将在第一个终端会话中显示:
Child 65016: Received SIGUSR1
child 正在按预期接收和处理信号。现在终止第一个 child:
# kill -TERM 65016
The output to the first terminal session will look like this (with different PIDS):
Child 65016: received SIGTERM
Parent: received SIGCHLD
Parent: child 65016 exited
Parent: starting child
Child 65039: started
新的 child 进程将在此时接收但响应任何信号,但无法捕获的 SIGKILL 和 SIGSTOP 除外。
向 parent 发送一个 SIGINT 将导致它向新的 child 发送一个 SIGTERM。 child 不会得到它并且 parent 将等到 child 在退出之前被强行杀死(是的,生产代码将包括超时和 SIGKILL 任何剩余的 child仁).
环境:
- Ubuntu 18.04.2
- macOS Mojave 10.14.3(相同行为)
- PHP 7.2.17 (cli)
我发现自己没有想法。想法?
编辑 2019 年 4 月 30 日 08:27 PDT:
我有更多的信息。我在 'echo "Child $pid: started\n";' 之后添加了一个 sleep( 10 ),让我有机会在 child.
上进行 运行 strace
根据 strace 输出,看起来信号正在传递,但 child 信号处理程序未被调用。
# sudo strace - p 69710
strace: Process 69710 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
rt_sigprocmask( SIG_SETMASK, [], ~[ KILL STOP RTMIN RT_1], 8) = 0
rt_sigaction( SIGINT, {sa_handler = SIG_IGN, sa_mask = [], sa_flags = SA_RESTORER, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ INT ], null, 8 ) = 0
rt_sigaction( SIGTERM, {sa_handler = 0x55730bdaf2e0, sa_mask = ~[ ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags = SA_RESTORER | SA_INTERRUPT | SA_SIGINFO, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ TERM ], null, 8 ) = 0
rt_sigaction( SIGUSR1, {sa_handler = 0x55730bdaf2e0, sa_mask = ~[ ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags = SA_RESTORER | SA_RESTART | SA_SIGINFO, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ USR1 ], null, 8 ) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, 0x7ffe79859470) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, {tv_sec = 37, tv_nsec = 840636107}) = ? ERESTART_RESTARTBLOCK( Interrupted by signal)
--- SIGUSR1 {si_signo = SIGUSR1, si_code = SI_USER, si_pid = 69544, si_uid = 1000} ---
rt_sigreturn({mask = []}) = -1 EINTR( Interrupted system call)
rt_sigprocmask( SIG_BLOCK, ~[ RTMIN RT_1], [], 8) = 0
rt_sigprocmask( SIG_SETMASK, [], null, 8 ) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, 0x7ffe79859470) = 0
我认为问题是 PHP 当在已注册的信号处理函数中调用 pcntl_fork 时,信号处理无法正常工作。由于第二个子进程是在 sigchldHandler
内部创建的,因此它不会 接收 处理后续信号。
编辑:很遗憾,我没有这方面的任何参考资料。我自己一直在用与 OP 类似的问题(因此是新帐户!)将我的头撞在墙上,我找不到任何关于这种行为的明确答案或解释,只是来自手动存根测试的证据。我也很想知道 (:
我正在开发 pre-forking TCP 套接字服务器,用 PHP 编写。
守护进程(parent 进程)派生一些 children 然后等待直到它被告知退出并且 children 全部消失或者它收到一个信号。
SIGINT 和 SIGTERM 导致它向所有 children 发送 SIGTERM。
child人设置了自己的信号处理程序:SIGTERM 导致干净退出。 SIGUSR1 导致它转储一些状态信息(只是在下面的示例代码中打印它收到信号)。
如果 child 意外退出,parent 将启动一个新的 child,除非退出标志已由 SIGINT 处理程序设置。
最初的 children,在守护程序初始化期间分叉,按预期对信号做出反应。
新分叉 children 以替换意外的 child 退出,不 响应信号。
下面的代码可以用来演示这一点:
<?php
$children = [];
$exiting = false;
pcntl_async_signals( true );
pcntl_signal( SIGCHLD, 'sigchldHandler' );
pcntl_signal( SIGINT, 'sigintHandler' );
pcntl_signal( SIGTERM, 'sigintHandler' );
// Fork our children.
for( $ii = 0; $ii < 1; $ii++ )
{
startChild();
}
// Forks a single child.
function startChild()
{
global $children;
echo "Parent: starting child\n";
$pid = pcntl_fork();
switch( true )
{
case ( $pid > 0 ):
$children[$pid] = $pid;
break;
case ( $pid === 0 ):
child();
exit( 0 );
default:
die( 'Parent: pcntl_fork() failed' );
break;
}
}
// As long as we have any children...
while( true )
{
if( empty( $children ) ) break;
sleep( 1 );
}
// The child process.
function child()
{
$pid = posix_getpid();
echo "Child $pid: started\n";
sleep( 10 ); // Give us a chance to start strace (4/30/19 08:27)
pcntl_sigprocmask( SIG_SETMASK, [] ); // Make sure nothing is blocked.
pcntl_async_signals( true ); // This may be inherited.
pcntl_signal( SIGINT, SIG_IGN ); // Ignore SIGINT.
pcntl_signal( SIGTERM, function() use ( $pid ) // Exit on SIGTERM.
{
echo "Child $pid: received SIGTERM\n";
exit( 0 );
}, false );
pcntl_signal( SIGUSR1, function() use( $pid ) // Acknowledge SIGUSR1.
{
printf( "Child %d: Received SIGUSR1\n", $pid );
});
// Do "work" here.
while( true )
{
sleep( 60 );
}
}
// Handle SIGCHLD in the parent.
// Start a new child unless we're exiting.
function sigchldHandler()
{
global $children, $exiting;
echo "Parent: received SIGCHLD\n";
while( true )
{
if( ( $pid = pcntl_wait( $status, WNOHANG ) ) < 1 )
{
break;
}
echo "Parent: child $pid exited\n";
unset( $children[$pid] );
if( !$exiting )
{
startChild();
}
}
}
// Handle SIGINT in the parent.
// Set exiting to true and send SIGTERM to all children.
function sigintHandler()
{
global $children, $exiting;
$exiting = true;
echo PHP_EOL;
foreach( $children as $pid )
{
echo "Parent: sending SIGTERM to $pid\n";
posix_kill( $pid, SIGTERM );
}
}
运行 这个脚本在终端会话中。初始输出将与此类似,但 PID 不同:
Parent: starting child
Child 65016: started
从不同的终端会话发出终止命令:
# kill -USR1 65016
child 进程将在第一个终端会话中显示:
Child 65016: Received SIGUSR1
child 正在按预期接收和处理信号。现在终止第一个 child:
# kill -TERM 65016 The output to the first terminal session will look like this (with different PIDS):
Child 65016: received SIGTERM
Parent: received SIGCHLD
Parent: child 65016 exited
Parent: starting child
Child 65039: started
新的 child 进程将在此时接收但响应任何信号,但无法捕获的 SIGKILL 和 SIGSTOP 除外。
向 parent 发送一个 SIGINT 将导致它向新的 child 发送一个 SIGTERM。 child 不会得到它并且 parent 将等到 child 在退出之前被强行杀死(是的,生产代码将包括超时和 SIGKILL 任何剩余的 child仁).
环境:
- Ubuntu 18.04.2
- macOS Mojave 10.14.3(相同行为)
- PHP 7.2.17 (cli)
我发现自己没有想法。想法?
编辑 2019 年 4 月 30 日 08:27 PDT:
我有更多的信息。我在 'echo "Child $pid: started\n";' 之后添加了一个 sleep( 10 ),让我有机会在 child.
根据 strace 输出,看起来信号正在传递,但 child 信号处理程序未被调用。
# sudo strace - p 69710
strace: Process 69710 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
rt_sigprocmask( SIG_SETMASK, [], ~[ KILL STOP RTMIN RT_1], 8) = 0
rt_sigaction( SIGINT, {sa_handler = SIG_IGN, sa_mask = [], sa_flags = SA_RESTORER, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ INT ], null, 8 ) = 0
rt_sigaction( SIGTERM, {sa_handler = 0x55730bdaf2e0, sa_mask = ~[ ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags = SA_RESTORER | SA_INTERRUPT | SA_SIGINFO, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ TERM ], null, 8 ) = 0
rt_sigaction( SIGUSR1, {sa_handler = 0x55730bdaf2e0, sa_mask = ~[ ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags = SA_RESTORER | SA_RESTART | SA_SIGINFO, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ USR1 ], null, 8 ) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, 0x7ffe79859470) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, {tv_sec = 37, tv_nsec = 840636107}) = ? ERESTART_RESTARTBLOCK( Interrupted by signal)
--- SIGUSR1 {si_signo = SIGUSR1, si_code = SI_USER, si_pid = 69544, si_uid = 1000} ---
rt_sigreturn({mask = []}) = -1 EINTR( Interrupted system call)
rt_sigprocmask( SIG_BLOCK, ~[ RTMIN RT_1], [], 8) = 0
rt_sigprocmask( SIG_SETMASK, [], null, 8 ) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, 0x7ffe79859470) = 0
我认为问题是 PHP 当在已注册的信号处理函数中调用 pcntl_fork 时,信号处理无法正常工作。由于第二个子进程是在 sigchldHandler
内部创建的,因此它不会 接收 处理后续信号。
编辑:很遗憾,我没有这方面的任何参考资料。我自己一直在用与 OP 类似的问题(因此是新帐户!)将我的头撞在墙上,我找不到任何关于这种行为的明确答案或解释,只是来自手动存根测试的证据。我也很想知道 (: