php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #34794 proc_close() hangs when used with two processes
Submitted: 2005-10-09 18:23 UTC Modified: 2007-02-13 19:54 UTC
Votes:9
Avg. Score:5.0 ± 0.0
Reproduced:8 of 8 (100.0%)
Same Version:3 (37.5%)
Same OS:3 (37.5%)
From: e-t172 at e-t172 dot net Assigned:
Status: Closed Package: Program Execution
PHP Version: 5CVS-2005-10-09 (snap) OS: Linux
Private report: No CVE-ID:
 [2005-10-09 18:23 UTC] e-t172 at e-t172 dot net
Description:
------------
(i am french, sorry for my bad english)

1. Open two processes with proc_open()
2. Try to close them : it works only if you close the second one first, otherwise it hangs

Reproduce code:
---------------
<?php
echo('Opening process 1'."\n");
$process1 = proc_open('cat', array(0 => array('pipe', 'r'), 1 => array('pipe', 'r')), $pipes1);
echo('Opening process 2'."\n");
$process2 = proc_open('cat', array(0 => array('pipe', 'r'), 1 => array('pipe', 'r')), $pipes2);

// WORKS :

//echo('Closing process 2'."\n");
//fclose($pipes2[0]); fclose($pipes2[1]); proc_close($process2);
//echo('Closing process 1'."\n");
//fclose($pipes1[0]); fclose($pipes1[1]); proc_close($process1);

// DOESN'T WORK :

echo('Closing process 1'."\n");
fclose($pipes1[0]); fclose($pipes1[1]); proc_close($process1);
echo('Closing process 2'."\n");
fclose($pipes2[0]); fclose($pipes2[1]); proc_close($process2);
?>

Expected result:
----------------
$ php -f test.php
Opening process 1
Opening process 2
Closing process 1
Closing process 2
$ 

Actual result:
--------------
$ php -f test.php
Opening process 1
Opening process 2
Closing process 1

(HANGS)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2005-10-09 20:12 UTC] e-t172 at e-t172 dot net
Same problem with the last CVS Snapshot.
 [2005-11-01 11:54 UTC] sniper@php.net
Assigned to the author of this stuff.
 [2006-04-26 14:50 UTC] radu dot rendec at ines dot ro
Same problem with 5.1.3RC4-dev (latest CVS snapshot at the moment) on Linux/i386.

I independently reproduced the bug with the following piece of code:

error_reporting(E_ALL);
$spec = array(
        0 => array("pipe", "r"),
        1 => array("pipe", "w"),
        2 => array("pipe", "w")
        );
$p1 = proc_open("/bin/cat", $spec, $fd1);
$p2 = proc_open("/bin/cat", $spec, $fd2);

fclose($fd1[0]);
fclose($fd1[1]);
fclose($fd1[2]);
echo "closing p1... "; flush();
proc_close($p1);
echo "success\n"; flush();

This code hangs in proc_close(). This doesn't happen if the second proc_open() is commented.

Although the parent process seems to correctly close all pipes, the child process still remains blocked in read(0,...).
 [2006-04-29 02:42 UTC] Dallas at ekkySoftware dot com

 [2006-05-28 17:32 UTC] jdolecek at NetBSD dot org
This is actually not a bug, or just documentation bug.

proc_close() blocks if the child has not terminated yet. 

You must use proc_terminate() instead of proc_close() if you can't guarantee the child has already exited, or use proc_get_status() to check if the child has already exited if you want to avoid blocking.
 [2006-05-28 19:20 UTC] jdolecek at NetBSD dot org
Actually yes, there is a severe pipe setup problem.

The problem is that the second spawned process inherits the descriptor of parent end of pipe to the first spawned process, created when setting up the process1 pipes. Since PHP doesn't set the close-on-exec flag, the descriptor stays open in process2.

So, when the parent-end of pipes1[0] is closed in master script, the descriptor is still open by process2, thus the pipe write end is not closed yet and thus cat in process1 doesn't end.

Note this is also potential security problem, since the second process is able to send data to the first.

Fix:

--- ext/standard/proc_open.c.orig   2006-05-28 19:10:35.000000000 +0200
+++ ext/standard/proc_open.c
@@ -929,6 +929,16 @@ PHP_FUNCTION(proc_open)
                            descriptors[i].mode_flags), mode_string, NULL);
 #else
                stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL);
+
+#if defined(F_SETFD) && defined(FD_CLOEXEC)
+               /*
+                * Mark the descriptor close-on-exec, so that it
+                * it won't be inherited by potential other children.
+                * This avoids first child deadlock on proc_close().
+                */
+               fcntl(descriptors[i].parentend, F_SETFD, FD_CLOEXEC);
+#endif
+
 #endif
                if (stream) {
                    zval *retfp;
 [2007-02-13 19:54 UTC] nlopess@php.net
This bug has been fixed in CVS.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.
 
Thank you for the report, and for helping us make PHP better.


 
PHP Copyright © 2001-2014 The PHP Group
All rights reserved.
Last updated: Sun Apr 20 19:01:51 2014 UTC