I have a php script that calls a go script. It gets results every 1-2 seconds, and print's them. Using php's exec and output, I only get the results when the program finishes. Is there a way I can check the output to see when it changes and output that while it's still running?
Something like this, but pausing the execution?:
$return_status = 0;
$output = [];
$old_output = ["SOMETHING ELSE"];
while ($return_status == 0) {
exec($my_program,$output,$return_status); #somehow pause this?
if $output != $old_output {
echo($output);
$old_output = $output;
}
}
Yes. Use the popen() function to get a file handle for the command's output, then read from it a line at a time.
Related
I used PHP to call python script successfully and got the result . But I have to wait for the end of script running without anything output. It looks not friendly to my customer.
How can I return the script results to the PHP web in realtime ?
For instance ,for code below , I want to the PHP web will show output message in realtime instead of show them together at the end . How can I change my code?
Thank you .
PHP Code:
<?php
$k = $_REQUEST['k'];
if (!empty($k))
{
$k = trim($k);
$a = array();
exec('python ./some.py '.$k, $a);
echo $a[0];
}
?>
Python Code:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
def do_some(a):
print 'test1'
time.sleep(30)
print 'test2'
if __name__ == '__main__':
print 'Now the python scritp running'
time.sleep(20)
a = sys.argv[1]
if a:
print 'Now print something'
T = do_some(a)
By specification, exec stop the calling program until the end of the callee. After that, you get back the output in a variable.
If you want to send data as soon as they are produced, you should use popen. It will fork a new process, but will not block the caller. So you can perform other tasks, like looping to read the sub-process output line by line to send it to your client. Something like that:
$handle = popen("python ./some.py ", 'r');
while(!feof($handle)) {
$buffer = fgets($handle);
echo "$buffer<br/>\n";
ob_flush();
}
pclose($handle)
I'm trying to learn long poll and I'm using following PHP script:
<?php
$lastMod = filemtime('test.txt');
$counter = 10;
while ($counter > 0) {
if (filemtime('test.txt') !== $lastMod) {
echo file_get_contents('./test.txt');
exit;
}
$counter--;
sleep(1);
}
echo $lastMod;
But no matter what I do or try, it doesn't do anything when I change test.txt content during script execution.
I would be really happy if somebody told me where is the mistake.
The filemtime result is cached during runtime, so you need to reset it explicitly using clearstatcache().
Run it right before your if.
From the docs:
Note: The results of this function are cached. See clearstatcache()
for more details.
I have a PHP script that takes the fields of a form to make 3 alternated program calls, where the output of the first one is the input of the second one and so on.
The problem is that I need to display the progress of each program call, nothing to complicated, I just like to show 3 different messages:
System call N waiting.
System call N in execution.
System call N finished.
I´m trying to do that with the different PHP functions like exec(), popen() or proc_open() but with these ones the browser waits until each call finish.
The whole system calls don´t take more than 5 minutes, maybe 3 or 4, so, it would also be good to place a timer in each call, maybe 1.5 minutes, and if the call takes more that that time, kill the current system call, skip the following calls and show an error message.
Do you have any idea? Maybe a combination of ajax and javascript can be a solution. Thanks in advance.
<?php
/*
System Calls
This file is required in another main script
$projectPath and $projectName defined in the main script
*/
//$mainHome = getcwd();
$home = $projectPath . $projectName;
$temp = $home . "/temp/";
$calls = $temp . "CALLS";
$threads = array();
if(is_dir($temp)){
//chdir($temp);
$FILE = fopen($calls, "r");
while(($call = fgetcsv($FILE)) !== FALSE) {
//print_r($call);
$threads[] = implode(" ", $call);
}
}
//print_r($threads);
$descriptorspec = array(0 => array("pipe","r"),
1 => array("pipe","w"),
2 => array("file","./phpError.log","a")
);
for ($a=0; $a<count($threads); $a++) {
print $threads[$a] . "<br/><br/>";
exec($threads[$a])
//$res = proc_open($threads[$a], $descriptorspec, $pipes, $temp);
}
//chdir($mainHome);
?>
Thank you very much! finally I develop a python CGI based solution, a bit more static than I thought but meets the expectations of the team.
Greetings
I'm trying to write a function that pings a few hundred addresses and returns their values (milliseconds). So far I've achieved the initial idea which is to ping and get the result but the problem arises when using the same code for hundreds of addresses, the PHP page stalls until it either times out or reaches the last ping command.
I would be glad if I could get some suggestions to output the results progressively, here is my current code:
<?php
// "for" loop added according to suggestion for browser compatibility (IE, FF, CHR, OPR, SFR)
for($i = 0; $i < 5000; $i++)
{
echo ' ';
}
function GetPing($ip = NULL) {
// Returns the client ping if no address has been passed to the function
if(empty($ip)) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// Check which OS is being run by the client
if(getenv('OS') == 'Windows_NT') {
//echo '<b>Detected local system:</b> Windows NT/2000/XP/2003/2008/Vista/7<p>';
$exec = exec("ping -n 1 -l 32 -i 128 " . $ip);
return end(explode(' ', $exec));
}
else {
//echo '<b>Detected local system:</b> Linux/Unix<p>';
$exec = exec("ping -c 1 -s 32 -t 128 " . $ip);
$array = explode('/', end(explode('=', $exec )));
return ceil($array[1]) . 'ms';
}
// ob_flush and flush added according to suggestion for buffer output
ob_flush();
flush();
}
// Added to test 20 sequential outputs
for($count = 0; $count < 20; $count++)
echo GetPing('8.8.8.8') . '<div>';
?>
After some feedback, I've added a for loop as well as ob_flush() and flush() to my script and I've also set output_buffering to 0 in php.ini. It seems to work for most browsers that I tested so far (IE8, Firefox 12, Chrome 19, Opera 11, Safari 5). It seems the current code is now working as intended but any suggestion to improve on it is immensely appreciated.
Thank you for your feedback.
this is just a guess; I've written years ago a very wobbly chat script that used output buffering as well (and fetching new messages in while(true) loop) ..
While doing this I've encountered the same problems that sometimes the script stalled (blank screen), sometimes it took a while until the characters appeared and additionally this was also browser specific.
Here are the relevant code snippets I've added to the script to have it work with IE6 and FF2 (as I said, years ago ...)
<?php
// End output buffering
ob_end_flush();
// IE and Safari Workaround
// They will only display the webpage if it's completely loaded or
// at least 5000 bytes have been "printed".
for($i=0;$i<5000;$i++)
{
echo ' ';
}
while( ... )
{
echo 'Message';
ob_flush();
flush();
}
?>
It worked for me, so maybe you could give it a try as well. (Altough I have no idea how modern browsers and server infrastrucutre will behave to this).
I think what you may be looking for is progressively running and outputting the script, rather than asynchronous functions.
See Is there a way to make PHP progressively output as the script executes?
Background
I am writing a simple online judge (a code grading system) using PHP and MySQL. It takes submitted codes in C++ and Java, compiles them, and tests them.
This is Apache running PHP 5.2 on an old version of Ubuntu.
What I am currently doing
I have a php program that loops infinitely, calling another php program by
//for(infinity)
exec("php -f grade.php");
//...
every tenth of a second. Let's call the first one looper.php and the second one grade.php. (Checkpoint: grade.php should completely finish running before the "for" loop continues, correct?)
grade.php pulls the earliest submitted code that needs to be graded from the MySQL database, puts that code in a file (test.[cpp/java]), and calls 2 other php programs in succession, named compile.php and test.php, like so:
//...
exec("php -f compile.php");
//...
//for([all tests])
exec("php -f test.php");
//...
(Checkpoint: compile.php should completely finish running before the "for" loop calling test.php even starts, correct?)
compile.php then compiles the program in test.[cpp/java] as a background process. For now, let's assume that it's compiling a Java program and that test.java is located in a subdirectory. I now have
//...
//$dir = "./sub/" or some other subdirectory; this may be an absolute path
$start_time = microtime(true); //to get elapsed compilation time later
exec("javac ".$dir."test.java -d ".$dir." 2> ".$dir
."compileError.txt 1> ".$dir."compileText.txt & echo $!", $out);
//...
in compile.php. It's redirecting the output from javac, so javac should be running as a background process... and it seems like it works. The $out should be grabbing the process id of javac in $out[0].
The real problem
I want to stop compiling if for some reason compiling takes more than 10 seconds, and I want to end compile.php if the program stops compiling before 10 seconds. Since the exec("javac... I called above is a background process (or is it?), I have no way of knowing when it has completed without looking at the process id, which should have been stored in $out earlier. Right after, in compile.php, I do this with a 10 second loop calling exec("ps ax | grep [pid].*javac"); and seeing if the pid still exists:
//...
$pid = (int)$out[0];
$done_compile = false;
while((microtime(true) - $start_time < 10) && !$done_compile) {
usleep(20000); // only sleep 0.02 seconds between checks
unset($grep);
exec("ps ax | grep ".$pid.".*javac", $grep);
$found_process = false;
//loop through the results from grep
while(!$found_process && list(, $proc) = each($grep)) {
$boom = explode(" ", $proc);
$npid = (int)$boom[0];
if($npid == $pid)
$found_process = true;
}
$done_compile = !$found_process;
}
if(!done_compile)
exec("kill -9 ".$pid);
//...
... which doesn't seem to be working. At least some of the time. Often, what happens is test.php starts running before the javac even stops, resulting in test.php not being able to find the main class when it tries to run the java program. I think that the loop is bypassed for some reason, though this may not be the case. At other times, the entire grading system works as intended.
Meanwhile, test.php also uses the same strategy (with the X-second loop and the grep) in running a program in a certain time limit, and it has a similar bug.
I think the bug lies in the grep not finding javac's pid even when javac is still running, resulting in the 10 second loop breaking early. Can you spot an obvious bug? A more discreet bug? Is there a problem with my usage of exec? Is there a problem with $out? Or is something entirely different happening?
Thank you for reading my long question. All help is appreciated.
I just came up with this code that will run a process, and terminate it if it runs longer than $timeout seconds. If it terminates before the timeout, it will have the program output in $output and the exit status in $return_value.
I have tested it and it seems to work well. Hopefully you can adapt it to your needs.
<?php
$command = 'echo Hello; sleep 30'; // the command to execute
$timeout = 5; // terminate process if it goes longer than this time in seconds
$cwd = '/tmp'; // working directory of executing process
$env = null; // environment variables to set, null to use same as PHP
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("file", "/tmp/error-output.txt", "a") // stderr is a file to write to
);
// start the process
$process = proc_open($command, $descriptorspec, $pipes, $cwd, $env);
$startTime = time();
$terminated = false;
$output = '';
if (is_resource($process)) {
// process was started
// $pipes now looks like this:
// 0 => writeable handle connected to child stdin
// 1 => readable handle connected to child stdout
// Any error output will be appended to /tmp/error-output.txt
// loop infinitely until timeout, or process finishes
for(;;) {
usleep(100000); // dont consume too many resources
$stat = proc_get_status($process); // get info on process
if ($stat['running']) { // still running
if (time() - $startTime > $timeout) { // check for timeout
// close descriptors
fclose($pipes[1]);
fclose($pipes[0]);
proc_terminate($process); // terminate process
$return_value = proc_close($process); // get return value
$terminated = true;
break;
}
} else {
// process finished before timeout
$output = stream_get_contents($pipes[1]); // get output of command
// close descriptors
fclose($pipes[1]);
fclose($pipes[0]);
proc_close($process); // close process
$return_value = $stat['exitcode']; // set exit code
break;
}
}
if (!$terminated) {
echo $output;
}
echo "command returned $return_value\n";
if ($terminated) echo "Process was terminated due to long execution\n";
} else {
echo "Failed to start process!\n";
}
References: proc_open(), proc_close(), proc_get_status(), proc_terminate()