I don't have much experience with PHP/mod_php administration, so I apologize if this is a really simple question.
My question is this - why won't a process that I've spawned from a PHP script via the exec() call receive an alarm interrupt properly?
The long version of my question:
This morning I was handed a bug in an existing php script. After some investigation, I've traced it down to the (apparent) fact that the php was using exec() to run a subprocess, the subprocess was relying on a SIGALRM to escape a loop, and it never received the alarm.
I don't think it matters, but the specific subprocess was /bin/ping. When pinging a device that doesn't return any packets (such as a device w/ a firewall that discards ICMP echo requests instead of returning destination host unreachable), you have to use the -w option to set a timer to allow the program to exit (because -c counts return packets - if the target never returns packets and you don't use -w, you're stuck in and endless loop). When called from the php, the alarm handler that ping -w relies on doesn't fire.
Here're a few interesting lines from using strace to follow the ping call from the command line (where the alarm handler does work):
(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) When I inserted a shell wrapper to allow me to run strace on the ping when called from the web, I found that the setitimer call is present (and appears to run successfully), but that the SIGALRM line and rt_sigreturn() lines aren't present. The ping then continues to run sendmsg() and recvmsg() forever until I kill it by hand.
Trying to reduce variables, I then cut ping out of it and wrote the following 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"; It works as expected when run from the command line, the alarm handler fires successfully:
[jj33@g3 t]# /tmp/toperl Mon May 2 14:49:04 2011 Starting sleep... Mon May 2 14:49:09 2011 ALARM, leaving Then I tried running /tmp/toperl via the same PHP page (via both exec() and backticks) that was having problems calling ping. Here's the php wrapper I wrote for the test:
<? print "Running /tmp/toperl via PHP\n"; $v = `/tmp/toperl`; print "Output:\n$v\n"; ?> As with ping, /tmp/toperl did not receive its alarm interrupt:
Running /tmp/toperl via PHP Output: Mon May 2 14:52:19 2011 Starting sleep... Mon May 2 14:52:29 2011 Exiting normally... Then I wrote a quick cgi wrapper in perl to execute in the same Apache, but under mod_cgi instead of mod_php. Here's the wrapper for reference:
[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"; And, lo and behold, the alarm handler worked:
Running /tmp/toperl Output: Mon May 2 14:55:34 2011 Starting sleep... Mon May 2 14:55:39 2011 ALARM, leaving So, back to my original question - why won't a process I've spawned via exec() in a mod_php controlled PHP script receive an alarm signal when the same spawned process will do so when called from the command line and perl/mod_cgi?
Apache 2.2.17, PHP 5.3.5.
Thanks for any thoughts.
EDIT - DerfK was correct, mod_php is masking out SIGALRM before calling the sub process. I don't have any interest in recompiling ping so I'll end up writing a wrapper for it. Since I already wrote so much text for this question I thought I would also drop in a revision to my toy program /tmp/toperl that tests to see if SIGALRM is being masked out and unblocking it if so.
#!/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"; Now this test works correctly (meaning it exits after 5 seconds with the "ALARM, leaving" line) in all instances (perl/command line, php/command line, perl/mod_cgi, php/mod_php). In the first three instances it prints the 'SIGALRM NOT being blocked' line, in the latter it prints 'SIGALRM is being blocked!' and correctly unblocks it.