23022

PHP: Need to close STDIN in order to read STDOUT?

Question:

I recently tried to communicate with a binary on my Ubuntu webserver [1] using the PHP function proc_open. I can establish a connection and define the pipes STDIN, STDOUT, and STDERR. Nice.

Now the bimary I am talking to is an interactive computer algebra software - therefore I would like to keep both STDOUT and STDIN alive after the first command such that I can still use the application a few lines later in an interactive manner (direct user inputs from a web-frontend).

However, as it turns out, the PHP functions to read the STDOUT of the binary (either stream_get_contents or fgets) need a closed STDIN before they can work. Otherwise the program deadlocks.

This is a severe drawback since I can not just reopen the closed STDIN after closing it. So my question is: why does my script deadlock if I want to read the STDOUT when my STDIN is still alive?

Thanks Jens

[1] <a href="https://stackoverflow.com/questions/24805645/proc-open-returns-false-but-does-not-write-in-error-file-permissions-issue" rel="nofollow">proc_open returns false but does not write in error file - permissions issue?</a>

my source:

$descriptorspec = array( 0 => array("pipe","r"), 1 => array("pipe","w"), 2 => array("file","./error.log","a") ) ; // define current working directory where files would be stored $cwd = './' ; // open reduce $process = proc_open('./reduce/reduce', $descriptorspec, $pipes, $cwd) ; if (is_resource($process)) { // some valid Reduce commands fwrite($pipes[0], 'load excalc; operator x; x(0) := t; x(1) := r;'); // if the following line is removed, the script deadlocks fclose($pipes[0]); echo "output: " . stream_get_contents($pipes[1]); // close pipes & close process fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); }

EDIT:

This code kind of works. Kind of because it uses usleeps to wait for the non-blocked STDOUT to be filled with data. How do I do that more elegantly?

@ Elias: By polling the $status['running'] entry you can only determine if the overall process is still running, but not if the process is busy or idling... That is why I have to include these usleeps.

define('TIMEOUT_IN_MS', '100'); define('TIMEOUT_STEPS', '100'); function getOutput ($pipes) { $result = ""; $stage = 0; $buffer = 0; do { $char = fgets($pipes[1], 4096); if ($char != null) { $buffer = 0; $stage = 1; $result .= $char; } else if ($stage == "1") { usleep(TIMEOUT_IN_MS/TIMEOUT_STEPS); $buffer++; if ($buffer > TIMEOUT_STEPS) { $stage++; } } } while ($stage < 2); return $result; } $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w") ) ; // define current working directory where files would be stored $cwd = './' ; // open reduce $process = proc_open('./reduce/reduce', $descriptorspec, $pipes, $cwd); if (is_resource($process)) { stream_set_blocking($pipes[1], 0); echo "startup output:<br><pre>" . getOutput($pipes) . "</pre>"; fwrite($pipes[0], 'on output; load excalc; operator x; x(0) := t; x(1) := r;' . PHP_EOL); echo "output 1:<br><pre>" . getOutput($pipes) . "</pre>"; fwrite($pipes[0], 'coframe o(t) = sqrt(1-2m/r) * d t, o(r) = 1/sqrt(1-2m/r) * d r with metric g = -o(t)*o(t) + o(r)*o(r); displayframe;' . PHP_EOL); echo "output 2:<br><pre>" . getOutput($pipes) . "</pre>"; // close pipes & close process fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); }

Answer1:

This reminds me of <a href="https://github.com/EVODelavega/gp-scripts/blob/master/autoRepeat.php" rel="nofollow">a script I wrote a while back</a>. While it might serve as inspiration to you (or others), it doesn't do what you need. What it <em>does</em> contain is an example of how you can read the output of a stream, without having to close any of the streams.<br /> Perhaps you can apply the same logic to your situation:

$allInput = array( 'load excalc; operator x; x(0) := t; x(1) := r;' );//array with strings to pass to proc if (is_resource($process)) { $output = ''; $input = array_shift($allInput); do { usleep(200);//make sure the running process is ready fwrite( $pipes, $input.PHP_EOL,//add EOL strlen($input)+1 ); fflush($pipes[0]);//flush buffered data, write to stream usleep(200); $status = proc_get_status($process); while($out = fread($pipes[1], 1024) && !feof($pipes[1])) $output .= $out; } while($status['running'] && $input = array_shift($allInput)); //proc_close & fclose calls here }

Now, seeing as I don't know what it is exactly you are trying to do, this code will need to be tweaked quite a bit. You may, for example, find yourself having to set the STDIN and STDOUT pipes as non-blocking.<br /> It's a simple matter of adding this, right after calling proc_open, though:

stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0);

Play around, have fun, and perhaps let me know if this answer was helpful in any way...

Answer2:

My guess would be that you're doing everything correctly, <em>except</em> that the binary is never notified that it has received all the input and can start to work. By closing STDIN, you're kicking off the work process, because it's clear that there will be no more input. If you're not closing STDIN, the binary is waiting for more input, while your side is waiting for its output.

You probably need to <strong>end your input with a newline</strong> or whatever other protocol action is expected of you. Or perhaps closing STDIN is <em>the</em> action that's expected of you. Unless the process is specifically created to stay open and continue to stream input, you can't make it do it. If the process reads all input, processes it, returns output and then quits, there's no way you can make it stay alive to process more input later. If the process explicitly supports that behaviour, there should be a definition on how you need to delimit your input.

Recommend

  • EditForm - How can I prevent submit on pressing enter key
  • Hide and show the div on particular path
  • Signal handling in Pylons
  • Sort bins from pandas cut
  • Gulp dest() dynamic destination folders based on file names
  • -(void)viewWillAppear:(BOOL)animated doesn't called
  • Create a Terminal-Based Bluetooth Monitor in XCode?
  • Gradle refresh removes source folders in build path
  • Vue.js 2: Vue cannot find files from /assets folder (v-for)
  • plot dirac function in matlab
  • Spring annotation @Order
  • Selenium Webdriver IE could not find element
  • Google TV VideoView playing YouTube rtsp videos
  • Excel File upload in asp.net using SqlBulkCopy
  • How to use array in autohotkey?
  • Reload Page with Javascript after Database changes
  • Generate and export point cloud from Project Tango
  • How to write seo friendly url's using htaccess?
  • Django, uWSGI & nginx: Process dies for “no reason”
  • using maven pom while creating jar:test-jar some times it says JAR will be empty - no content was ma
  • Google Spreadsheet Script to Blink a range of Cells
  • How to make 100% div height between header and footer?
  • JavaScript Regex to Match Boundaries of Words with diacritics
  • How to turn off notice reporting in xampp?
  • Terminal run dalvikvm with am.jar
  • How do I add a mouse over tooltip to an Image using .DrawImage()
  • VSTS work items list through REST API
  • How to decleare char *const argv[] in swift [duplicate]
  • Unity3d lost directional light shadows after generate assetBundle (.unity3d file)
  • Using Service Component Runtime
  • Angular FormGroup won't update it's value immediately after patchValue or setValue
  • How to mutate multiple variables without repeating codes?
  • ARKit code issue {unknown error -1=ffffffffffffffff error: Task failed with exit 1}
  • XSLT Transformation to validate rules in XML document