17

I have a number of scripts that echo progress as they execute because they are long running. Each basically does the following at the end of each looped row of data processed:

echo '.';
@ob_flush();
flush();

This was working just fine for years and then I upgraded to PHP 5.3.x and Apache 2.2.x across several servers. Now, even if I pad the buffer with white space or set "ob_implicit_flush(1)", I can't get it to show the output on command.

One server still shows the output, but it is in chunks. It can take nearly 5 minutes and then suddenly a string of dots appears on the screen. With the other servers, I get nothing until the script finishes executing entirely.

I've tried looking through the php.ini and httpd.conf files to see if I could figure out what had changed between the different servers, but I'm obviously missing something.

I've also tried disabling mod_deflate in .htaccess for the affected scripts, but that doesn't help either (disabling mod_gzip used to fix the problem right away).

Can someone point me in the right direction with this please? Not being able to monitor script execution in real time is causing all sorts of problems but we can't stay on these older PHP versions any longer.

On an even more peculiar side note, I did try downgrading a server to PHP 5.2.17 but the output buffer problem remained after the downgrade. This makes me suspect it is something relating to the way Apache is handling PHP output since Apache 2 was left in place.

5
  • an alternative approach - who really wants to sit there while you script runs, why not run it in the background, and let the 'user' know when its done. That's my policy for anything that takes more than a few seconds Commented Dec 6, 2012 at 20:18
  • I do that with some, but can't with others. Some of these scripts I need to check the output on as they process for any signs the script needs to be stopped before completion. Some of these process some pretty complex data that changes regularly. Commented Dec 6, 2012 at 21:29
  • could be run command line then no browser\server buffering issues with the cli. Commented Dec 6, 2012 at 21:35
  • What Apache version were you running when it worked the way you wanted? Commented Aug 20, 2015 at 15:55
  • Apache 2.2 under some configuration, I think, but not sure. This question is so old, I've since given up and migrated to Nginx with Laravel commands and background Beanstalkd queues paired with AJAX progress bars. None of these techniques have the flushing problem and are much easier to maintain. Commented Aug 20, 2015 at 16:27

5 Answers 5

13

ob_flush() (an flush()) only flush the PHP buffer - the webserver maintains a buffer itself. And weird as it may sound, flushing the buffer early actually decreases throughput on the server, hence recent versions of apache buffer more agressively. There's also horrendous problems relating to compression and partial rendering when working with HTTP chunked encoding.

If you want to incrementally add content to a page then use ajax or websockets to add it a bit at a time.

Sign up to request clarification or add additional context in comments.

3 Comments

And thusly, all my latest scripts use such methods. However, I'm dealing with a sh*t ton of legacy code so I'm badly looking for a quick workaround due to lack of time. Coding gods be merciful if I actually have to rewrite some of these beasts:(
I'd accept this answer, as it does a good job explaining the issue and the downsides (which I'm now 2 years-in-the-trenches wiser on), but it seems there is a bounty out for a real way to get around it and I don't want to step on toes. Up vote for this though. :)
It was rather a long time ago I wrote this. You might want to try decreasing the DeflateBufferSize (for mod_deflate, but you still need to fill the buffer before it will send) and from your php code while (ob_get_level()) ob_end_flush(); flush();
8

This issue has more to do with your server (apache) rather than the php version.

One option is to disable output buffering, though performance might suffer in other parts of the site

On Apache

Set the php ini directive (output_buffering=off) from your server config, including a .htaccess file. So I used the following in a .htaccess file to disable the output_buffering just for that one file:

<Files "q.php">
    php_value output_buffering Off
</Files>

And then in my static server configuration, I just needed AllowOverride Options=php_value (or a larger hammer, like AllowOverride All) in order for that to be allowed in a .htaccess file.

On Nginx

To Disable buffering for Nginx (add "proxy_buffering off;" to the config file and restart Nginx

3 Comments

This solve the problem, should be part of the accepted answer.
proxy_buffering off; is the key if you have Nginx+Apache setup. In Nginx-only setup, you need fastcgi_buffering off; instead. For mod_fcgid.
I get "Internal Server Error" with that in .htaccess
5

Most probably the change as described in the original question is that the new setup was using FastCgi (http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html) and this has buffering turned on be default.

But there are other factors to check:

If you are using Fcgid then this also has buffering: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidoutputbuffersize

If your charsets between PHP and Apache don't match - this can hold things up.

mod_deflate and mod_gzip also buffer (as mentioned in original question)

So steps to check are:

  1. Flush the PHP buffer - (as described in the question)

  2. Turn off the Apache buffering - Add php_value output_buffering off in .htaccess

  3. Disable mods that buffer for deflation - Disable mod_deflate and mod_gzip

  4. Ensure your char encoding matches between PHP and Apache - add default_charset = "utf-8"; in php.ini and AddDefaultCharset utf-8 in httpd.conf)

  5. Disable buffering in FastCgi or Fcgid - You can disable buffering in FastCgi by adding the -flush option. Details in the link above. Option for Fcgid also listed above.

As far as I know, those are the only buffers on the server; obviously other devices between server and browser may also buffer, e.g. a proxy may wait for the full output to be provided before passing it on. Fiddler (https://www.telerik.com/fiddler) does this, and it frequently floors me until I remember.

1 Comment

if you have a Nginx frontend to Apache, you should add proxy_buffering off; to nginx.conf too. Btw, this solution is working perfectly. Still looking for a solution with mod_proxy_fcgid.c
5

I tried everything to get this to work, including all known settings listed above. I've been trying to use PHP to serve chunked video files using HTTP_RANGE and it wasn't working.

After pulling most of my hair out, i found the answer: you have to output at least one byte more than the buffer size in order for it to output to the browser. Here's the script that ended up working for me:

<?php 
// Close open sessions
session_write_close();

// Turn off apache-level compression
@apache_setenv('no-gzip', 1);

// Turn off compression
@ini_set('zlib.output_compression', 0);

// Turn error reporting off
@ini_set('error_reporting', E_ALL & ~ E_NOTICE);

// Tell browser not to cache this
header("Cache-Control: no-cache, must-revalidate");

// close any existing buffers
while (ob_get_level()) ob_end_clean();

// Set this to whatever you like
$buffer = 8096;

for($i = 1; $i <= 8; $i++) 
{
    // Start a output buffer with specified size
    ob_start(null,$buffer,PHP_OUTPUT_HANDLER_FLUSHABLE);
    // Output exactly one byte more than that size 
    // \n == 2 bytes, so 8096-1+2 = 8097
    echo str_repeat('=', $buffer-1)."\n";
    // 0.25s nap
    usleep(250000);
    // End output buffering and flush it
    ob_end_flush();
    flush();
}

Hope this helps someone!

1 Comment

Thank you so much for posting this! I've been banging my head trying to figure this out for days now with it working locally but not on multiple remote servers. I tried various answers from every post I could find and this is the only thing that worked!
4
+50

There is a possible work around for this issue that doesn't require you to edit your existing scripts or modify your server configuration to stop buffering output. By using a wrapper script you can start your long running process in the background from the php script serving the web request. Then you can pipe the output of the long running process into a text file which can be easily read to find the current progress of the script via polling. Example below:

Long running process script

<?php
// long_process.php
echo "I am a long running process ";
for ($i = 0; $i < 10; $i++) {
    echo ".";
    sleep(1);
}
echo " Processing complete";
?>

Script to initialize the long running process and watch output

<?php
    // proc_watcher.php
    $output = './output.txt';
    if ($_GET['action'] == 'start') {
        echo 'starting running long process<br>';
        $handle = popen("nohup php ./long_process.php > $output &", 'r');
        pclose($handle);
    } else {
        echo 'Progress at ' . date('H:i:s') . '<br>';
        echo file_get_contents($output);
    }
    $url = 'proc_watcher.php';
?>
<script>
    window.setTimeout(function() {
         window.location = '<?php echo $url;?>';
    }, 1000);
</script>

If you send a web request to proc_watcher.php?action=start the script should start the long running process in the background and then return the contents of the output file to the web browser every second.

The trick here is the command line nohup php ./long_process.php > ./output.txt &, which runs the process in the background and sends the output to a file instead of STDOUT.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.