Terminating proc_open after x Seconds - php

I am using pdftk to merge pdf files. Occasionaly a user uploads a ill formed pdf and it hangs the process returning no errors and consuming all the server resources. To prevent this i am looking at implementing the process call through proc_open and wish to set a time limit for the process to run and terminate the process if it exceeds the time limit.
Below is an example of the function that I am using to merge the pdf files if I set
stream_set_blocking($process, 0);
it returns an error:
stream_set_blocking(): supplied resource is not a valid stream resource
I presume something in this function is malformed and hope someone will be able to point me in the right direction... The function currently isn't returning any errors however does not terminate after 30 seconds as required
protected function pdf_merge($documents,$output_file,$time = 30){
$end = time() + $time;
$cmd = sprintf('/usr/local/bin/pdftk %s cat output %s', $documents, $output_file);
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file","./error.log","a")
);
$process = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
stream_set_blocking($pipes[1], 0);
while (!feof($pipes[1]) && (time() < $end)) {
fwrite($pipes[0], stream_get_contents($pipes[0]));
fclose($pipes[0]);
$pdf_content = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$return_value = proc_close($process);
return $return_value;
}
error_log('file is taking too long... kill process');
proc_terminate();
}
}

Perhaps I'm off base here, does most of this code run?
fwrite($pipes[0], stream_get_contents($pipes[0]));
Looks like you are trying to write to the pipe whatever you can read from the pipe...
Also, you may want to check with get_proc_status() as the command may have completed by the time you try to set the blocking...

Related

php -> fwrite to process pipe hangs -> why?

Can anyone say, why the following code hangs on fwrite($pipes[0], $data);, but it does not hang when I change $bytesCount to, for example, 1000?
I was not able to find answer via google :(
Thank you.
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w")
);
$bytesCount = 1000000;
$process = proc_open('cat', $descriptorspec, $pipes);
$data = str_repeat('a', $bytesCount);
fwrite($pipes[0], $data);
fclose($pipes[0]);
$response = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$return_value = proc_close($process);
Pipes are implemented with input and output buffers. cat starts to read, and copies everything to the output. When the output buffer is full, its write is blocked.
Since nothing is reading cat's input (as that line is never reached), it will block indefinitely, blocking your fwrite.

Run Function if Different Function Fails to Complete Within Time Frame

I already have a function on a website for a client that grabs data from a live server to save it locally, but something I didn't expect was that sometimes these local servers aren't in an area with good service, so every now and then the script dies after a certain amount of time because it failed to connect.
I already have a system implemented to disable external calls for these types of situations, but the clients can't get to the option to set this "Offline Mode" to begin with because the service is bad and the server is trying to reach the live server.
So what I need to do, is wrap my SyncTable function with a function like set_time_limit(8) that calls a different function that sets the "Offline Mode" automatically if the SyncTable Function fails to complete in the 8 seconds.
Is something like this possible? If so, I would love to know how so I can save these clients some time in areas with rough service.
You can accomplish this using proc_open, proc_get_status, and proc_terminate to start the SyncTable operation as a process, monitor it, and terminate it if necessary. Note: You may need to create a simple wrapper script so you can start the SyncTable function as a standalone process.
Here's a function I use to do this and enforce a timeout:
/// Executes a command and returns the output
/// If the timeout value (in seconds) is reached, it terminates the process
/// and returns FALSE
function exec_timeout($cmd, $timeout=30)
{
$descriptors = array(
0 => array('pipe', 'r'), // stdin
1 => array('pipe', 'w'), // stdout
2 => array('pipe', 'w') // stderr
);
$pipes = Array();
$process = proc_open($cmd, $descriptors, $pipes);
$result = '';
$end_time = time() + $timeout;
if (is_resource($process))
{
// set the streams to non-blocking
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
$timeleft = $end_time - time();
while ($timeleft > 0)
{
$status = proc_get_status($process);
$result .= stream_get_contents($pipes[1]);
// leave the loop if the process has already finished
if (!$status['running'])
break;
$timeleft = $end_time - time();
}
if ($timeleft <= 0)
{
proc_terminate($process);
$result = FALSE;
}
}
// check for errors
$errors = stream_get_contents($pipes[2]);
if (!empty($errors))
fwrite(STDERR, "$errors\n");
// close streams
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $result;
}

PHP SVN update - TortoiseSVN

New code:
<?php
exec('"C:\Program Files\TortoiseSVN\bin\svn.exe" update "c:\wamp\www\project"');
This results in an infinite loop, no result is returned. What am I doing wrong?
== edit ==
On Windows, I'm trying to update a project by using PHP. I'm having problems using the commandline: I want visual feedback (important in case of conflicts), so I don't want to start as a background process. Is this possible?
The code I have so far is:
<?php
$todo = "cd \"C:\\Program Files\\TortoiseSVN\\bin\\\"";
$todo2 = "START TortoiseProc.exe /command:update /path:\"C:\\wamp\\www\\project\\\" /closeonend:0";
pclose(popen($todo, "r"));
pclose(popen($todo2, "r"));
I would drop exec and use proc_open (see http://php.net/manual/en/function.proc-open.php)
Here's an example I quickly whipped up and which should work for you:
<?php
// setup pipes that you'll use
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("pipe", "w") // stderr
);
// call your process
$process = proc_open('"C:\Program Files\TortoiseSVN\bin\svn.exe" update "c:\wamp\www\project"',
$descriptorspec,
$pipes);
// if process is called, pipe data to variables which can later be processed.
if(is_resource($process))
{
$stdin = stream_get_contents($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
$return_value = proc_close($process);
}
// Now it's up to you what you want to do with the data you've got.
// Remember that this is merely an example, you'll probably want to
// modify the output handling to your own likings...
header('Content-Type: text/plain; charset=UTF-8');
// check if there was an error, if not - dump the data
if($return_value === -1)
{
echo('The termination status of the process indicates an error.'."\r\n");
}
echo('---------------------------------'."\r\n".'STDIN contains:'."\r\n");
echo($stdin);
echo('---------------------------------'."\r\n".'STDOUTcontains:'."\r\n");
echo($stdout);
echo('---------------------------------'."\r\n".'STDERR contains:'."\r\n");
echo($stderr);
?>
Aside:
The line
// call your process
$process = proc_open('"C:\Program Files\TortoiseSVN\bin\svn.exe" update "c:\wamp\www\project"',
$descriptorspec,
$pipes);
could also be escaped like this
// call your process
$process = proc_open("\"C:\\Program Files\\TortoiseSVN\\bin\\svn.exe\" update \"c:\\wamp\\www\\project\"",
$descriptorspec,
$pipes);
which might or might not solve some problems on some systems that have hickups with the single brackets (') and spaces () in the notation.

Run .exe in web app and real-time communication with this .exe

My PHP web app receives data from a stream. Once the page is loaded I need to open an .exe file using system() or exec() and after short period of time new data will come, so I must type specific command to this .exe to get its returned value, how can I do this?
I'm only able to do this manually in command prompt
path/to/.exe :: hit 'Enter'
command1 params1
//...
What you're looking for is proc_open(). http://php.net/manual/en/function.proc-open.php
This will allow you to work with STDIO streams to communicate with the separate process.
Example from the PHP documentation:
$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
);
$cwd = '/tmp';
$env = array('some_option' => 'aeiou');
$process = proc_open('php', $descriptorspec, $pipes, $cwd, $env);
if (is_resource($process)) {
// $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
fwrite($pipes[0], '<?php print_r($_ENV); ?>');
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
// It is important that you close any pipes before calling
// proc_close in order to avoid a deadlock
$return_value = proc_close($process);
echo "command returned $return_value\n";
}
You could also consider shared memory if you need more than one listener, but this scenario sounds like you would benefit from using a queue.
Documentation msg_get_queue, msg_receive, msg_send
Example
// Send
if (msg_queue_exists(12345)) {
$mqh = msg_get_queue(12345);
$result = msg_send($mqh , 1, 'data', true);
}
// Receive
$mqh = msg_get_queue(12345, 0666);
$mqst = msg_stat_queue($mqh);
while ($mqst['msg_qnum']) {
msg_receive($mqh, 0, $msgtype, 2048, $data, true);
// Spawn your process
$mqst = msg_stat_queue($mqh);
}
Edit
Semaphore functions aren't available on Windows, as suggested above your best bet is to go with popen (unidirectional) or proc_open for bi-directional support.

Using proc_open function in PHP

I am trying to execute a TCL script from PHP. I am using PHP's proc_open for the communication .But I am unable to get the result from the tcl script .
Can someone go through the code and let me know where I am going wrong ?
PHP code
<?php
$app = 'tclsh84.exe';
$spec = array(array("pipe", "r"), array("pipe", "w"), array("pipe", "w"));
$process = proc_open($app, $spec, $pipes);
if (is_resource($process))
{
fwrite($pipes[0], 'source sum.tcl ');
fwrite($pipes[0], 'tclsh test.tcl ');
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
// echo fread($pipes[1],1024).'<hr>';
proc_close($process);
}
?>
//sum.tcl
proc sum {arg1 arg2} {
set x [expr {$arg1 + $arg2}];
return $x
}
//test.tcl
puts " the sum is [sum 10 9 ] "
You're not passing newlines to the application (fwrite($pipes[0], "source sum.tcl\n")), could that be the cause? Otherwise make sure to check all return values of your function calls. You should fail early, if the first fwrite() fails, for example.

Categories