X

Буферизация вывода и flush

Столкнулся с проблемой, которая не дает мне покоя, уже не первый раз. Но вот пришло время её решить.

Не работает flush(); на хостинге заказчика.

Тестовый пример:

    <?php  
        error_reporting(-1);  
        ini_set('display_errors','On');  
        ini_set('output_buffering', 'Off');  
        ini_set('output_handler', '');  
        ini_set('zlib.output_compression', 'Off');  
        ini_set('implicit_flush', 'On');  
        ob_implicit_flush(true);  

        for ($i=1; $i<5; $i++) {  
            echo $i.') Delay '.($i*2).' sec<br />';   
            flush();  
            sleep($i*2);  
        }  
    ?>

Есть доступ к конфигурации php.ini

    output_buffering = Off  
    ;output_handler =   
    zlib.output_compression = Off  
    ;zlib.output_handler =   
    implicit_flush = Off  
    safe_mode = Off

PHP работает как модуль Апача.

Работающие модули ISP Manager

ZendExtensionManager.so     bz2.so          calendar.so  
cgi-fcgi.so                 ctype.so        curl.so  
date.so                     dbase.so        eaccelerator.so  
exif.so                      filter.so       ftp.so  
gd.so                       gettext.so      gmp.so  
hash.so                     iconv.so        json.so  
libxml.so                   mbstring.so     mysql.so  
mysqli.so                   openssl.so      pcntl.so  
pcre.so                     pdo.so          pdo_mysql.so  
pdo_sqlite.so               readline.so     zlib.so  
reflection.so               session.so      shmop.so  
simplexml.so                sockets.so      spl.so  
standard.so                 tokenizer.so    xml.so  
zip.so

PHP Info:

Configure command:

    ./configure'  
    '--build=x86_64-redhat-linux-gnu'       '--host=x86_64-redhat-linux-gnu'        '--target=x86_64-redhat-linux-gnu'  
    '--program-prefix='                     '--prefix=/usr'                         '--exec-prefix=/usr'  
    '--bindir=/usr/bin'                     '--sbindir=/usr/sbin'                   '--sysconfdir=/etc'  
    '--datadir=/usr/share'                  '--includedir=/usr/include'             '--libdir=/usr/lib64'  
    '--libexecdir=/usr/libexec'             '--localstatedir=/var'                  '--sharedstatedir=/usr/com'  
    '--mandir=/usr/share/man'               '--infodir=/usr/share/info'             '--cache-file=../config.cache'  
    '--with-libdir=lib64'                   '--with-config-file-path=/etc'          '--with-config-file-scan-dir=/etc/php.d'  
    '--disable-debug'                       '--with-pic'                            '--disable-rpath'  
    '--without-pear'                        '--with-bz2'                            '--with-exec-dir=/usr/bin'  
    '--with-freetype-dir=/usr'              '--with-png-dir=/usr'                   '--with-xpm-dir=/usr'  
    '--enable-gd-native-ttf'                '--with-t1lib=/usr'                     '--without-gdbm'  
    '--with-gettext'                        '--with-gmp'                            '--with-iconv'  
    '--with-jpeg-dir=/usr'                  '--with-openssl'                        '--with-pcre-regex'  
    '--with-zlib'                           '--with-layout=GNU'                     '--enable-exif'  
    '--enable-ftp'                          '--enable-magic-quotes'                 '--enable-sockets'  
    '--enable-sysvsem'                      '--enable-sysvshm'                      '--enable-sysvmsg'  
    '--with-kerberos'                       '--enable-ucd-snmp-hack'                '--enable-shmop'  
    '--enable-calendar'                     '--without-mime-magic'                  '--without-sqlite'  
    '--with-libxml-dir=/usr'                '--with-xml'                            '--with-system-tzdata'  
    '--with-apxs2=/usr/sbin/apxs'           '--without-mysql'                       '--without-gd'  
    '--disable-dom'                         '--disable-dba'                         '--without-unixODBC'  
    '--disable-pdo'                         '--disable-xmlreader'                   '--disable-xmlwriter'  
    '--disable-json'                        '--without-pspell'                      '--disable-wddx'  
    '--without-curl'                        '--disable-posix'                       '--disable-sysvmsg'  
    '--disable-sysvshm'                     '--disable-sysvsem'

Core:

    output_buffering    no value    no value  
    output_handler      no value    no value

Zlib:

    zlib.output_compression         Off Off  
    zlib.output_compression_level   -1    -1  
    zlib.output_handler    no value    no  value

Пошел я в гугл, долго искал, читал и пробовал.. Ничего не помогает.. Я грешил на какую-то буферизирующую софтинку, и решил в очередной раз проверить, не загоняет ли сервак свои ответы в gzip, т.к. это обозначало бы что они кешируются.. И вот что я увидел

GET /module/cron/test.php HTTP/1.1  
Host: site.ru  
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: ru,en-us;q=0.7,en;q=0.3  
Accept-Encoding: gzip,deflate  
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7  
Keep-Alive: 115  
Connection: keep-alive  
Cookie: conn[db]=db; conn[user]=user; conn[pwd]=pass; dle_newpm=0; PHPSESSID=blnpk7ijpvrnumobn443aomvr7  
Cache-Control: max-age=0  

HTTP/1.1 200 OK  
Server: nginx/0.7.65  
Date: Sat, 18 Sep 2010 10:04:03 GMT  
Content-Type: text/html; charset=cp1251  
Transfer-Encoding: chunked  
Connection: keep-alive  
Keep-Alive: timeout=20  
X-Powered-By: PHP/5.2.14

Кеширования нет, но .. Server: nginx
Вот оно что, в качестве фронтенда, стоял nginx, и я заподозрил именно его в таком поведении.

Нашел конфиг, он находился в /etc/nginx/nginx.conf, посмотрел там, и нашел вот это:

    proxy_buffer_size 4k;  
    proxy_buffers 120 64k;  
    proxy_busy_buffers_size 64k;  
    proxy_temp_file_write_size 64k;

С конфигурацией nginx я столкнулся впервые, знал лишь то, что разработал его Игорь Сысоев и он русский, а это значит, что должно быть достаточно мануалов. Пошел в поиск и попал к нему на сайт, там меня интересовал раздел документации, а именно модуль ngx_http_proxy_module. Вот там я и нашел ответ на свой вопрос, а именно директиву proxy_buffering:

Директива разрешает использовать буферизацию ответа проксируемого сервера. Если буферизация включена, то nginx принимает ответ проксируемого сервера как можно быстрее, сохраняя его в буфера, заданные директивами proxy_buffer_size и proxy_buffers. Если ответ не помещается полностью в память, то его часть записывается на диск.

Если буферизация выключена, то ответ синхронно передаётся клиенту сразу же по мере его поступления. nginx не пытается считать весь ответ проксируемого сервера, максимальный размер данных, который nginx может принять от сервера задаётся директивой proxy_buffer_size.

Пошел искать эту директиву в своем конфиге, но её там небыло. Ну, раз нет, тогда добавим и я её прописал в секции Main location:

proxy_buffering off;

(!) Не забудьте про точку запятую в конце. Я забыл, и в начале подумал, что это лекарство мне не помогло.

Конфигурация, сохранена. Остается вопрос, как же рестартануть nginx с правильной конфигурацией. Это делается таким образом (в консоли):
1) #ps ax | grep nginx
2) ищем в выданном нам списке nginx: master process
3) запоминаем его pid (это число в начале строки), пусть это будет 7889
4) #top
5) теперь нажимаем k
6) На вопрос PID to kill отвечаем, номером нашего процесса, т.е. 7889, нажимаем Enter
7) На вопрос Kill PID 7889 with signal [15]: отвечаем HUP, нажимаем Enter (подробнее про сигналы тут)
8) нажимаем Ctrl+C для выхода из top'a
9) Можно тестировать наш скрипт с flush

У меня все заработало и я был счастлив! Как всегда, проблема которую не можешь решить несколько дней, решается всего одной маленькой строчкой 🙂

Категории: Apache nginx Сервера

Комментарии (10)

  • А в .htaccess добавить строку:
    php_flag output_buffering off
    разве не решает проблему?

    • В некоторых случаях это решит проблему, но конкретно в описываемом, думаю, что нет. Т.к. кеширование происходило на стороне nginx-а, а не php.

  • Да, на Денвере работает. А, например, в windows server 2012 стоит IIS8 и размер буфера по умолчанию 4МБ. Его, конечно, можно заполнить подобным образом через echo str_pad(' ', 4294967296); , но в этом случае вывод текста происходит только первый раз, а всё остальное только в конце скрипта. Причём даже если поставить str_pad в цикл. Да и гонять постоянно 4МБ тоже нагрузка.
    В настройках IIS8 буферизация вывода так запросто не отключается. Буферизацию в IIS8 можно выключить или уменьшить, если поправить файл
    C:\Windows\System32\inetsrv\config\schema\IIS_schema.xls.
    Но у админа нет прав на его запись, и даже при остановленном IIS система показывает access denied. И в свойствах файла права не устанавливаются.
    Доступ можно открыть, выполнив следующий батник

    SET DIRECTORY_NAME="C:\Windows\System32\inetsrv\config\schema"
    TAKEOWN /f %DIRECTORY_NAME% /r /d y
    ICACLS %DIRECTORY_NAME% /grant administrators:F /t
    PAUSE

    В самом файле IIS_schema.xls значение «bufferingOn» ставим false. Значения «bufferingLimit», «responseBufferLimit», «receiveBufferLimit» по умолчанию установлены 4194304. Меняем на 4096. Если ещё меньше, то IIS не запускается, тип Integer у него предполагает значение от 4096, о чём он и сообщает при попытке запуска.

    После этих исправлений в файле IIS_schema.xls, наконец, пошёл динамический вывод текста на php 5.6 на IIS8. Этим самым способом, когда один раз выполняем

    echo str_pad(' ', 1024);
    @ob_flush();
    flush();

    А потом просто flush();

    • Спасибо, что поделились рецептом! Я практически не работал с конфигурацией IIS, так что особо мне нечего добавить. Однако уверен, что многим посетителям Ваш совет пригодится!

  • У меня flush(); заработал вот так:

    <?php
    echo str_pad('',1024);
    @ob_flush();
    flush();

    Это для первого вывода. А дальше где надо можно уже просто flush();

  • isica, а будет ли всё это работать если нет никакого апача.
    Если Nginx и PHP как FastCGI????

  • isica, дружище, огромное тебе спасибо, ну просто нереально огромное спасибо !

  • Автору респект!
    Мне тоже довелось убить целый день для решения этого вопроса.
    Добавлю, что отключить буферизацию Nginx можно также прямо из php-скрипта, посредством заголовка X-Accel-Buffering.
    А вот с буферами Apache все не так гладко. Мне пришлось добавить
    while (ob_get_level()) { ob_end_flush(); }
    --без этого не работало.
    Кроме того, отключить zlib через ini_set удается не на всех серверах.

    Вот наиболее универсальный вариант:

    <?php
    header('X-Accel-Buffering: no');
    ini_set('output_buffering', 'Off');
    ini_set('output_handler', '');
    ini_set('zlib.output_handler','');
    ini_set('zlib.output_compression', 'Off');
    # ini_set('implicit_flush', 'On');
    while (ob_get_level()) { ob_end_flush(); }
    ob_implicit_flush(true);
    for ($i=1; $i<5; $i++) {
    echo $i.') Delay 2 sec';
    sleep(2);
    }
    ?>

  • Ты невнимательно прочитал статью, там никто процесс не убивает и даже не рестартует. Ему только посылается команда HUP для "обновления конфигурации". Об этом подробнее написано в мануале на сайте nginx - Управление nginx:

    Для того, чтобы nginx перечитал файл конфигурации, нужно послать главному процессу сигнал HUP. Главный процесс сначала проверяет синтаксическую правильность конфигурации, а затем пытается применить новую конфигурацию, то есть, открыть лог-файлы и новые listen сокеты. Если ему это не удаётся, то он откатывает изменения и продолжает работать со старой конфигурацией. Если же удаётся, то он запускает новые рабочие процессы, а старым шлёт сообщение о плавном выходе. Старые рабочие процессы закрывают listen сокеты и продолжают обслуживать старых клиентов. После обслуживания всех клиентов старые рабочие процессы завершаются.

  • О господи.. Автору позор, совет - читать маны нужно до конца, килять веб процессы не есть хорошо!

    познаём новое:
    /etc/init.d/nginx restart (server Debian based)
    /etc/init.d/php-fpm restart (после изменений php.ini, имя процесса пхп у Вас может быть и другим)

    Пишу для новичков, чтобы учились всё правильно делать..)) Удачи)