Por que um manipulador de alarme não dispara em um processo executado a partir de um processo php?

4

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.

    
por jj33 02.05.2011 / 22:17

1 resposta

3

Mod_php provavelmente bloqueia isso (usando sigprocmask() presumo, as máscaras são mantidas através de fork() e execve() ) para evitar que os sinais destruam o Apache (já que mod_php executa PHP no processo do apache).

Se é devido a sigprocmask (), então eu acho que você deveria ser capaz de usar o módulo POSIX do perl para desfazê-lo no script exec () 'd, mas não sei exatamente como isso funciona. O Cookbook de Perl um exemplo de bloqueio e desbloqueio de SIGINT. Eu acho que deveria ser algo como

use POSIX qw(:signal_h);
$sigset=POSIX::SigSet->new(SIGALRM);
sigprocmask(SIG_UNBLOCK,$sigset);

Se isso não funcionar, talvez tente instalar o php5-cgi, configurando-o como um manipulador no Apache para uma extensão diferente (digamos, .phpc), renomeando o script para ping.phpc e atualizando os links. Como o CGI é executado em seu próprio processo, a versão CGI do PHP pode não bloquear os sinais.

    
por 03.05.2011 / 00:58