Eu não tenho muita experiência com a administração PHP / mod_php, então peço desculpas se esta é uma questão muito simples.
Minha pergunta é: por que um processo que eu gero de um script PHP através da chamada exec () recebe uma interrupção de alarme corretamente?
A versão longa da minha pergunta:
Esta manhã recebi um bug em um script php existente. Depois de algumas investigações, descobri até o (aparente) fato de que o php estava usando exec () para executar um subprocesso, o subprocesso estava contando com um SIGALRM para escapar de um loop e nunca recebeu o alarme.
Eu não acho que isso importe, mas o subprocesso específico era / bin / ping. Ao fazer ping de um dispositivo que não retorna nenhum pacote (como um dispositivo com firewall que descarta solicitações de eco ICMP em vez de retornar o host de destino inacessível), é necessário usar a opção -w para definir um cronômetro para permitir que o programa exit (porque -c conta return packets - se o alvo nunca retorna pacotes e você não usa -w, você está preso em um loop infinito). Quando chamado pelo php, o manipulador de alarmes em que ping -w
confia não é acionado.
Aqui estão algumas linhas interessantes de usar strace
para seguir a chamada ping da linha de comando (onde o manipulador de alarmes funciona):
(snip)
setitimer(ITIMER_REAL, {it_interval={0, 0}, it_value={1, 0}}, NULL) = 0
(snip)
--- SIGALRM (Alarm clock) @ 0 (0) ---
rt_sigreturn(0xe) = -1 EINTR (Interrupted system call)
Quando eu inseri um wrapper de shell para permitir que eu execute strace no ping quando chamado pela web, descobri que a chamada setitimer
está presente (e parece ser executada com êxito), mas que a linha SIGALRM e rt_sigreturn ( ) linhas não estão presentes. O ping então continua a rodar sendmsg () e recvmsg () para sempre até que eu o mate à mão.
Tentando reduzir as variáveis, cortei o ping dele e escrevi o seguinte perl:
[jj33@g3 t]# cat /tmp/toperl
#!/usr/bin/perl
$SIG{ALRM} = sub { print scalar(localtime()), " ALARM, leaving\n"; exit; };
alarm(5);
print scalar(localtime()), " Starting sleep...\n";
sleep (10);
print scalar(localtime()), " Exiting normally...\n";
Funciona como esperado quando executado a partir da linha de comando, o manipulador de alarme é acionado com êxito:
[jj33@g3 t]# /tmp/toperl
Mon May 2 14:49:04 2011 Starting sleep...
Mon May 2 14:49:09 2011 ALARM, leaving
Então eu tentei executar o / tmp / toperl através da mesma página do PHP (via exec () e backticks) que estava tendo problemas ao chamar o ping. Aqui está o wrapper php que eu escrevi para o teste:
<?
print "Running /tmp/toperl via PHP\n";
$v = '/tmp/toperl';
print "Output:\n$v\n";
?>
Como com o ping, o / tmp / toperl não recebeu sua interrupção de alarme:
Running /tmp/toperl via PHP
Output:
Mon May 2 14:52:19 2011 Starting sleep...
Mon May 2 14:52:29 2011 Exiting normally...
Então eu escrevi um wrapper cgi rápido em perl para executar no mesmo Apache, mas sob mod_cgi ao invés de mod_php. Aqui está o wrapper para referência:
[jj33@g3 t]# cat tt.cgi
#!/usr/bin/perl
print "Content-type: text/plain\n\n";
print "Running /tmp/toperl\n";
my $v = '/tmp/toperl';
print "Output:\n$v\n";
E eis que o manipulador de alarme funcionou:
Running /tmp/toperl
Output:
Mon May 2 14:55:34 2011 Starting sleep...
Mon May 2 14:55:39 2011 ALARM, leaving
Então, voltando à minha pergunta original - por que um processo que eu criei via exec () em um script PHP mod_php controlado recebe um sinal de alarme quando o mesmo processo gerado irá fazê-lo quando chamado da linha de comando e perl / mod_cgi?
Apache 2.2.17, PHP 5.3.5.
Obrigado por qualquer pensamento.
EDIT - O DerfK estava correto, o mod_php está mascarando o SIGALRM antes de chamar o subprocesso. Eu não tenho nenhum interesse em recompilar o ping, então vou acabar escrevendo um wrapper para ele. Como eu já escrevi tanto texto para essa pergunta, eu pensei em colocar uma revisão em meu programa de brinquedo / tmp / toperl que testa para ver se o SIGALRM está sendo mascarado e desbloqueá-lo em caso afirmativo.
#!/usr/bin/perl
use POSIX qw(:signal_h);
my $sigset_new = POSIX::SigSet->new();
my $sigset_old = POSIX::SigSet->new();
sigprocmask(SIG_BLOCK, $sigset_new, $sigset_old);
if ($sigset_old->ismember(SIGALRM)) {
print "SIGALRM is being blocked!\n";
$sigset_new->addset(SIGALRM);
sigprocmask(SIG_UNBLOCK, $sigset_new);
} else {
print "SIGALRM NOT being blocked\n";
}
$SIG{ALRM} = sub { print scalar(localtime()), " ALARM, leaving\n"; sigprocmask(SIG_BLOCK, $sigset_new, $sigset_old); exit; };
alarm(5);
print scalar(localtime()), " Starting sleep...\n";
sleep (10);
print scalar(localtime()), " Exiting normally...\n";
Agora este teste funciona corretamente (ou seja, ele sai após 5 segundos com a linha "ALARM, leaving") em todas as instâncias (perl / linha de comando, php / linha de comando, perl / mod_cgi, php / mod_php). Nos três primeiros casos, imprime a linha 'SIGALRM NOT being blocked', no segundo, a mensagem 'SIGALRM está sendo bloqueada!' e desbloqueia corretamente.