8

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?

Seva Alekseyev
  • 59,826
  • 25
  • 160
  • 281
  • 1
    No, proc_open() doesn't block anything. But there are a few problems here: proc_close() may fail to close process and block your PHP application. If that is the case try to close all opened pipes before trying proc_close() or/and use proc_terminate() + proc_get_status() at least for debugging - they don't block execution of script. Overall HTTP request execution depends on web server that you are using - it is possible to implement blocking of the request untill all PHP's child processes are terminated, but at least Apache/Nginx don't do that. – XzKto Aug 18 '11 at 09:30
  • Tx. Pipes are not a problem - I'm not passing any. – Seva Alekseyev Aug 18 '11 at 13:06
  • Then why are you using proc_open instead of exec/system/passthru etc. ? Their behaviour is much more transparent. – XzKto Aug 18 '11 at 13:31
  • passthru() passes the output into the HTTP response - I don't want that. Exec() and system() wait till process completion. I, meanwhile, want to start a background process that might take a while (around 1 min), and get on with HTTP processing. If the process outlives the current HTTP request, so be it. – Seva Alekseyev Aug 18 '11 at 14:04
  • No, exec() and system() wait for process completion only if you don't run it in background - try exec("top -b -n 1000 > /dev/null 2> /dev/null &"); – XzKto Aug 18 '11 at 14:08
  • Good point. Make an answer, I'll accept. – Seva Alekseyev Aug 18 '11 at 14:10
  • I'm not sure how to phrase an answer that will be usefull for other users(your question and accepted answer are completely unrelated and it seems there was no problem to begin with, just wrong approach) so I think you should just close/delete it. – XzKto Aug 18 '11 at 14:20
  • How about: "It's not about proc_open, it's about placing the & at the end of the command line. If you do, the process is in the background and no blocking takes place." – Seva Alekseyev Aug 18 '11 at 14:27

2 Answers2

16

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.

XzKto
  • 2,472
  • 18
  • 18
  • Could you please explain what does this phrase mean? > PHP doesn't run your shell script instantly, so you have to wait a bit before executing other shell commands – Meglio Jan 08 '14 at 22:10
  • 1
    When you do something like "proc_open(SHELL_CMD); runPhpFunction();" in PHP, runPhpFunction() can be executed before SHELL_CMD actually starts to run in the system. While proc_open() appears to be synchronous as it returns the SHELL_CMD's PID(which is impossible to do before the SHELL_CMD starts), but this is not actually your SHELL_CMD's PID, it is PID of the SHELL(/bin/sh or whatever) that the SHELL_CMD will be executed in, and it may take some time for SHELL to actually execute SHELL_CMD(and thus cause race condition), but most of the time it is fast enough that it appears to run in order. – XzKto Jan 10 '14 at 07:56
1

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).

Peeter
  • 9,282
  • 5
  • 36
  • 53