Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does PHP proc_open block the Web request?

Tags:

php

process

By default, on Linux, does creating a process via proc_open() make the PHP script not terminate until the spawned process terminates? I don't want it to, and I close the process handle right away.

proc_open itself does not block, that's clear enough. But what about the overall HTTP request execution?

like image 650
Seva Alekseyev Avatar asked Aug 18 '11 03:08

Seva Alekseyev


2 Answers

I had some time on the weekend, so I made a little research on proc_open() on *nix systems.

While proc_open() doesn't block PHP's script execution even if shell script is not run in background PHP automatically invokes proc_close() after PHP's script is completely executed if you don't invoke it yourself. So, we can imagine that we have always a line with proc_close() in the end of script.

The problem lies with unobvious, but logical proc_close() behaviour. Let's imagine we have a script like:

$proc = proc_open('top -b -n 10000',
                array(
                    array('pipe', 'r'),
                    array('pipe', 'w')),
                $pipes);
//Process some data outputted by our script, but not all data
echo fread($pipes[1],100);
//Don't wait till scipt execution ended - exit
//close pipes   
array_map('fclose',$pipes);
//close process
proc_close($proc);

Strange, proc_close() whould wait before shell script execution ended but our script was soon terminated. It hapens because we closed pipes(it seems that PHP does it silently if we forgot) so as soon as that script tries to write something to already non-existent pipe - it gets an error and terminates.

Now, let's try without pipes (well, there will be, but they will use current tty without any link to PHP):

$proc = proc_open("top -b -n 10000", array(), $pipes);
proc_close($proc);

Now, our PHP script is waiting for our shell script to end. Can we avoid it? Luckily PHP spawns shell scripts with

sh -c 'shell_script'

so, we can just kill out sh process and leave our script running:

$proc = proc_open("top -b -n 10000", array(), $pipes);
$proc_status=proc_get_status($proc);
exec('kill -9 '.$proc_status['pid']);
proc_close($proc);

Of cource we could just have run the process in background like:

$proc = proc_open("top -b -n 10000 &", array(), $pipes);
proc_close($proc);

and not have any problems, but this feature leads us to the most complex question: can we run a process with proc_open() read some output and then force the process to background? Well, in a way - yes.

Main problem here is pipes: we can't close them or our process will die, but we need them to read some usefull data from that process. It turns out that we can use a magic trick here - gdb.

First, create a file somewhere(/usr/share/gdb_null_descr in my example) with following contents:

p dup2(open("/dev/null",0),1)
p dup2(open("/dev/null",0),2)

It will tell gdb to change descriptors 1 and 2(well, they are usually stdout and stderr) to new file handlers (/dev/null in this example, but you can change it).

Now, last thing: make sure gdb can connect to other running processes - it is default on some systems, but for example on ubuntu 10.10 you have to set /proc/sys/kernel/yama/ptrace_scope to 0 if you are don't run it as root.

Enjoy:

$proc = proc_open('top -b -n 10000',
                array(
                    array('pipe', 'r'),
                    array('pipe', 'w'),
                    array('pipe', 'w')),
                $pipes);
//Process some data outputted by our script, but not all data
echo fread($pipes[1],100);

$proc_status=proc_get_status($proc);

//Find real pid of our process(we need to go down one step in process tree)
$pid=trim(exec('ps h -o pid  --ppid '.$proc_status['pid']));

//Kill parent sh process
exec('kill -s 9 '.$proc_status['pid']);

//Change stdin/stdout handlers in our process
exec('gdb -p '.$pid.' --batch -x /usr/share/gdb_null_descr');

array_map('fclose',$pipes);
proc_close($proc);

edit: I forgot to mention that PHP doesn't run your shell script instantly, so you have to wait a bit before executing other shell commands, but usually it is fast enough(or PHP is slow enough) and I'm to lazy to add that checks to my examples.

like image 126
XzKto Avatar answered Nov 16 '22 19:11

XzKto


I ran into a similar problem and wrote a small script to handle it:

https://github.com/peeter-tomberg/php-shell-executer

What I did was background the process and still have access to the result of the backgrounded process (both stderr and stdout).

like image 1
Peeter Avatar answered Nov 16 '22 19:11

Peeter