write->read

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

write->read

Anton Maksimenkov
Hi.

 Есть задача - апач пишет в лог, а мне надо из этого лога читать и
обрабатывать. Плюс к тому периодически (по времени) скидывать
результаты далее.
 То есть просто сделать popen на 'tail -f /.../access.log' я не могу,
т.к. если не будет поступлений в лог можно "проспать" время очередного
сброса результатов.
 Есть идея - использовать неблокирующий ввод и сидеть на poll(). По
времени посылать себе alarm(x). poll() выскочит и далее по ходу - толи
это просто сигнал и просто надо скинуть общий результат, толи были ещё
и данные, их можно обработать и потом скинуть общий результат.

 Вопрос, можно ли положиться на следующее развитие событий: если я на
неблокирующем вводе получаю сведение "есть данные", значит ли это, что
их писатель (апач в моём случае) записал ВСЮ строку, т.е. если я начну
read, то получу всю строку, а не какой-то её кусок?

 Предположение сделано из man write(2), что write в файл на диске либо
запишет строку целиком, либо - это уже серьёзная ошибка и в нормальном
режиме такого не должно быть. Соответственно либо будет записана вся
строка, либо ничего. Или например диск заполнен, тогда тоже понятно,
что придётся меры принимать. Но это ужё критические случаи, не то.
 Зачем это? Это затем, что очень не хочется мутить с "кольцевым
буфером" или ещё чем-то в таком роде, с до-чтениями в поисках "\n" и
выделения строки и т.д., а заюзать fgets, брать оттуда "готовую"
строку и обрабатывать.
 Вариант с popen на 'tail -f' хочется обойти, ибо мне кажется что
здесь есть потеря в скорости, а также перегоны данных из шелла - в
ядро - в процесс, это лишняя нагрузка и затраты ресурсов. Хотя может
есть ещё варианты?
 Однако есть сомнение - в случае если ядро, уйдя на write() в
процессе-апаче, на полдороги переключится в мой процесс, который ждёт
в read()'е, и данные какие-то уже есть - может быть я прочту их (а это
как раз не вся строка, а часть). Под это сомнение предположений нет,
это как бы "боязнь" такая. Это может произойти?



 Может я частично отвечу, что я обходил похожий случай, там был
accept() (здесь это будет "применить блокирующий ввод") и в
обработчике сигнала указывал, что сигнал прерывает системные вызовы.
Таким образом accept() прерывался, затем надо рассмотреть результат,
errno, и в одном из случаев это будет означать "данных нет, просто
прервано сигналом". А может сигнал прийти как раз после accept() но
перед следующей командой. То есть далее нужно обрабатывать "а может
сигнал пришел, и время скидывать результат настало", иначе это может
быть последние данные, потом будет пусто, а сигнал уже был...
 А может прийти ПОСЛЕ ПОСЛЕДНЕЙ команды цикла обработки но ДО
accept(), тогда уже точно заблокируемся и проспим... Значит надо
делать некий "промежуток запаса", то есть если до сигнала осталось
примерно 10 сек, то лучше уже сейчас сбросить результат и поставить
alarm() на следующее время сброса...
 А если система перегружена, то может статься, что и более 10 сек
пройдёт между последней командой и alarm() - тут уж точно "проспим"...

 Одним словом выходит как-то неказисто, куча кода, весьма далекая от
"самодокументирования". Если окомментарить, вообще вагон писанины. Я и
хотел этого избежать, если возможно.
--
engineer


Reply | Threaded
Open this post in threaded view
|

Re: write->read

Mike Belopuhov
On Wed, Nov 30, 2005 at 17:09 +0500, [hidden email] wrote:
> Hi.
>

мне к сожалению, такие опусы осиливать просто лень :)
но предположу что kevent это то, что вам нужно.

пример есть на http://www.openbsd.ru/prog/


Reply | Threaded
Open this post in threaded view
|

Re: write->read

Dmitry Bogdan
In reply to this post by Anton Maksimenkov
On Wed, 30 Nov 2005 17:09:55 +0500
 [hidden email] wrote:
> Hi.
>
>  Есть задача - апач пишет в лог, а мне надо из этого
> лога читать и обрабатывать. Плюс к тому
> периодически (по времени) скидывать результаты
> далее.
Мне кажется, apache умеет кидать логи в пайп другой
программе, и это можно как-нибудь использовать.


Reply | Threaded
Open this post in threaded view
|

Re[2]: write->read

Anton Maksimenkov
In reply to this post by Mike Belopuhov
Hi, Mike.

> мне к сожалению, такие опусы осиливать просто лень :)
> но предположу что kevent это то, что вам нужно.
> пример есть на http://www.openbsd.ru/prog/

Вопрос:
-------------------------------------------------------------------
Может ли быть так, что write ПРОЦЕССА "писателя" НЕ ЗАКОНЧИТ ПИСАТЬ
ВСЮ СТРОКУ, А ЯДРО УЖЕ ПЕРЕКЛЮЧИТСЯ НА read ПРОЦЕССА "читателя"?
-------------------------------------------------------------------
Как это явно смоделировать мне не понятно, ведь паузу внутри write()
поставить не получится.


 Смысл в том, что хоть select/poll, хоть kevent делает что - выйдет из
блокировки как только появятся данные. Если это произойдёт ДО того как
строка будет ПОЛНОСТЬЮ записана, и сразу выполнится read, то он
прочтет НЕ ВСЮ строку (а только ту часть, которая успела записаться).
--
engineer


Reply | Threaded
Open this post in threaded view
|

Re: write->read

Alexander Yurchenko
On Wed, Nov 30, 2005 at 08:09:06PM +0500, [hidden email] wrote:

> Hi, Mike.
>
> > мне к сожалению, такие опусы осиливать просто лень :)
> > но предположу что kevent это то, что вам нужно.
> > пример есть на http://www.openbsd.ru/prog/
>
> Вопрос:
> -------------------------------------------------------------------
> Может ли быть так, что write ПРОЦЕССА "писателя" НЕ ЗАКОНЧИТ ПИСАТЬ
> ВСЮ СТРОКУ, А ЯДРО УЖЕ ПЕРЕКЛЮЧИТСЯ НА read ПРОЦЕССА "читателя"?
> -------------------------------------------------------------------

Да. Там куча мест где процесс может уснуть. Но poll(2) в другом процессе
вернется только когда первый процесс полностью закончит запись. Я правда
могу ошибаться, но судя по коду ядра должно быть так.

> Как это явно смоделировать мне не понятно, ведь паузу внутри write()
> поставить не получится.

Запишите большой буфер, магабайт.

>
>
>  Смысл в том, что хоть select/poll, хоть kevent делает что - выйдет из
> блокировки как только появятся данные. Если это произойдёт ДО того как
> строка будет ПОЛНОСТЬЮ записана, и сразу выполнится read, то он
> прочтет НЕ ВСЮ строку (а только ту часть, которая успела записаться).
> --
> engineer
>

--
   Alexander Yurchenko


Reply | Threaded
Open this post in threaded view
|

Re[2]: write->read

Anton Maksimenkov
In reply to this post by Dmitry Bogdan
Hi, Dmitry.

>>  Есть задача - апач пишет в лог, а мне надо из этого
>> лога читать и обрабатывать. Плюс к тому
>> периодически (по времени) скидывать результаты
>> далее.
> Мне кажется, apache умеет кидать логи в пайп другой
> программе, и это можно как-нибудь использовать.

Но вопрос остается - а если в "писатель" пайп запишет только часть
строки, ядро переключится на "читателя", читатель прочтёт только эту
часть...

И вот ещё. Пробовал я cronolog пользовать (там как раз в пайп логи
уходят). Так вот с ним апач не стопорится, остается висеть какой-то
процесс. А так как у меня несколько разных апачей работает, пришлось
убивать ВСЕХ и запускать ВСЕХ. Ну, конечно можно было поизвращаться
поискать там группы, то, сё. Но перезапускать-то регулярно надо.

Мне после таких выкрутасов сразу разонравились "логи в пайп".

А задача несколько шире, я кратко описал. Этот анализатор
1) может работать не всё время, т.е. может быть остановлен и запущен
позже - он "догоняет" процесс записи и далее идёт с ним в ногу.
2) результаты сохраняются в базу через некие промежутки времени, и для
ускорения работы промежуточные результаты хранятся в памяти. поэтому
если произойдёт сбой он просто берет файл, добегает до даты последнего
сброса и продолжает обработку.

Всё очень удобно, когда есть под рукой файл, который можно подхватить,
дойти до места сбоя/остановки и продолжить обработку как ни в чём не
бывало.
--
engineer


Reply | Threaded
Open this post in threaded view
|

Re: write->read

Grigory Klyuchnikov
In reply to this post by Anton Maksimenkov
Hi,

[hidden email] wrote:

>Вопрос:
>-------------------------------------------------------------------
>Может ли быть так, что write ПРОЦЕССА "писателя" НЕ ЗАКОНЧИТ ПИСАТЬ
>ВСЮ СТРОКУ, А ЯДРО УЖЕ ПЕРЕКЛЮЧИТСЯ НА read ПРОЦЕССА "читателя"?
>-------------------------------------------------------------------
>  
>
Как известно, чтение и запись производится блоками, если строка меньше
размера
блока, то этого не произойдет.
А вообще советую обратиться к первоисточникам, например,
М.Бах "Архитектура операционной системы UNIX"  
http://www.citforum.ru/operating_systems/bach/contents.shtml,
Стивенс "Advanced Programming in the UNIX Environment" (название
перевода не помню)

>Как это явно смоделировать мне не понятно, ведь паузу внутри write()
>поставить не получится.
>
>
> Смысл в том, что хоть select/poll, хоть kevent делает что - выйдет из
>блокировки как только появятся данные. Если это произойдёт ДО того как
>строка будет ПОЛНОСТЬЮ записана, и сразу выполнится read, то он
>прочтет НЕ ВСЮ строку (а только ту часть, которая успела записаться).
>  
>
Конечно, такое возможно... тем более, что read, вообще говоря, не
предназначена для чтения строк, она читает блок, размер которого
задается параметром функции, и сколько есть для чтения, столько и отдаст.
А успел записать кто-то или нет - никаких гарантий. Так по-моему :)

--
С уважением,
Григорий Ключников


Reply | Threaded
Open this post in threaded view
|

Re: Re[2]: write->read

Dinar Talypov
In reply to this post by Anton Maksimenkov

>Но вопрос остается - а если в "писатель" пайп запишет только часть
>строки, ядро переключится на "читателя", читатель прочтёт только эту
>часть...

>И вот ещё. Пробовал я cronolog пользовать (там как раз в пайп логи
>уходят). Так вот с ним апач не стопорится, остается висеть какой-то
>процесс. А так как у меня несколько разных апачей работает, пришлось
>убивать ВСЕХ и запускать ВСЕХ. Ну, конечно можно было поизвращаться
>поискать там группы, то, сё. Но перезапускать-то регулярно надо.

>Мне после таких выкрутасов сразу разонравились "логи в пайп".

>А задача несколько шире, я кратко описал. Этот анализатор
>1) может работать не всё время, т.е. может быть остановлен и запущен
>позже - он "догоняет" процесс записи и далее идёт с ним в ногу.
>2) результаты сохраняются в базу через некие промежутки времени, и для
>ускорения работы промежуточные результаты хранятся в памяти. поэтому
>если произойдёт сбой он просто берет файл, добегает до даты последнего
>сброса и продолжает обработку.

>Всё очень удобно, когда есть под рукой файл, который можно подхватить,
>дойти до места сбоя/остановки и продолжить обработку как ни в чём не
>бывало.

все это я когда то писал, только анализировал результаты того что сложил
flow-capture из flow-tools. Через IPC работает. Сперва писатель пишет потом
выставаляет флаг в буфере сооющений что есть данные, а тот кто читает
смотрит на этот флаг и считывает. По моему можно еще с семафорами поигратся,
тога вообще будет супер :). Главное чтоб цкль оправдывала все усилия
приложенные к достижению этой цели ;)


Reply | Threaded
Open this post in threaded view
|

Re[2]: write->read

Anton Maksimenkov
In reply to this post by Alexander Yurchenko
Hi, Alexander.

>> Вопрос:
>> -------------------------------------------------------------------
>> Может ли быть так, что write ПРОЦЕССА "писателя" НЕ ЗАКОНЧИТ ПИСАТЬ
>> ВСЮ СТРОКУ, А ЯДРО УЖЕ ПЕРЕКЛЮЧИТСЯ НА read ПРОЦЕССА "читателя"?
>> -------------------------------------------------------------------
> Да. Там куча мест где процесс может уснуть. Но poll(2) в другом процессе
> вернется только когда первый процесс полностью закончит запись. Я правда
> могу ошибаться, но судя по коду ядра должно быть так.
>> Как это явно смоделировать мне не понятно, ведь паузу внутри write()
>> поставить не получится.
> Запишите большой буфер, магабайт.

Я попробовал было. Ниже код. Если убрать poll(), то работает.
А именно, при появлении данных в файле читает их в буфер кусочками,
используя "сдвиг от начала буфера".
Когда буфер заполняется "сдвиг от начала буфера" обнуляется и всё
повторяется. И так до тех пор пока не прочитано BS байт.

 Именно эти BS байт другой процесс (писатель) пишет в файл куском.

Добавив poll(), я ожидал, что при запуске оно уснёт на poll() и будет
ждать данных в файле. Однако оно не ждёт, сразу начнают бежать строки
#./reader
poll() returned 1
poll() returned 1
..
И бегут. Потом "писатель" пишет, этот читает и завершается.


Подскажите, почему poll() не ждёт?


#=========================== reader

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <unistd.h>

#include "ext.h" // Содержит только #define BS число

int
main (int argc, char **argv)
{
        char *buf;
        int outf;
        int rd, readed, entire;
        int bufsize = BS;
        struct pollfd pfd[1];

        bufsize = BS / 20;

        if ((buf = malloc(bufsize * 10)) == NULL)
                err(1, "Can't allocate memory");
        printf("bufsize\t%d\n", bufsize);

        if ((outf = open("file", O_RDWR, 0755)) == -1)
                err(1, "open()");

        pfd[0].fd = outf;
        pfd[0].events = POLLRDNORM;

        entire = 0;
        while (entire < BS) {
                readed = 0;
                rd = -1;
                while (readed < bufsize) {
--------------------------------------------------------------------
                        if ((rd = poll(pfd, 1, -1)) == -1 || (pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL)))
                                err(1, "poll()");
                        printf("poll() returned %d\n", rd);
--------------------------------------------------------------------
                        if ((rd = read(outf, buf + readed, bufsize - readed)) != (bufsize - readed))
                                if (rd == -1)
                                        err(1, "read()");

                        readed += rd;
                        entire += rd;
                        if (rd > 0) {
                                printf("read()\t%d of %d\n", rd, bufsize);
                                printf("entire\t%d bytes\n", entire);
                        }
                }
        }
        printf("DONE:\nread() %d of %d\n", entire, BS);

        if (close(outf) == -1)
                warn("open()");
        free(buf);

        exit(0);
}





На всякий случай.
#=========================== writer
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <unistd.h>

#include "ext.h"

int
main (int argc, char **argv)
{
        char *buf;
        int outf, tmp;
        int writed, written;
        int bufsize = BS;


        if ((outf = open("file", O_CREAT | O_RDWR, 0755)) == -1)
                err(1, "open()");
        sleep(10); // Чтобы работа пошла явно ПОСЛЕ запуска reader'a

        if ((buf = malloc(bufsize)) == NULL)
                err(1, "Can't allocate memory");
        for (tmp = 0; tmp < bufsize - 1; tmp++) {
                writed = tmp;
                if (writed > 128)
                        writed = writed % 128;

                buf[tmp] = writed;
        }

        written = 0;
        while (written < BS) {
                if ((writed = write(outf, buf + written, bufsize - written)) != (bufsize - written))
                        if (writed == -1)
                                err(1, "write()");
                        else
                                warnx("write() %d bytes of %d", writed, bufsize - written);

                written += writed;
        }
        printf("write() %d bytes of %d\n", written, bufsize);
        printf("done\n");

        if (close(outf) == -1)
                warn("open()");
        free(buf);

        exit(0);
}

--
engineer


Reply | Threaded
Open this post in threaded view
|

Re: write->read

Oleg Safiullin
> Добавив poll(), я ожидал, что при запуске оно уснёт на poll() и будет
> ждать данных в файле. Однако оно не ждёт, сразу начнают бежать строки
> #./reader
> poll() returned 1
> poll() returned 1
> ..
> И бегут. Потом "писатель" пишет, этот читает и завершается.
>
>
> Подскажите, почему poll() не ждёт?

Особенность работы poll с регулярными файлами.
Для данной задачи лучше использовать механизм kevent и следить за событием "расширение файла".
Пример можно посмотреть тут:

http://www.openbsd.ru/prog/

так и называется - kevent


Reply | Threaded
Open this post in threaded view
|

Re[2]: write->read

Anton Maksimenkov
Hi, Oleg.

>> Добавив poll(), я ожидал, что при запуске оно уснёт на poll() и будет
>> ждать данных в файле. Однако оно не ждёт, сразу начнают бежать строки
>> #./reader
>> poll() returned 1
>> poll() returned 1
>> ..
>> И бегут. Потом "писатель" пишет, этот читает и завершается.
>> Подскажите, почему poll() не ждёт?

> Особенность работы poll с регулярными файлами.
> Для данной задачи лучше использовать механизм kevent и следить за событием "расширение файла".
> Пример можно посмотреть тут:
> http://www.openbsd.ru/prog/
> так и называется - kevent

Спасибо, Олег, сработало.

Результат басни такой: пока write() писателя не завершится, у читателя
не будет ни read(), ни kevent(). То есть подтверждается сказанное
Александром - событие наступит после окончания write().

 За исключением poll(), которое и правда оказалось заразой. А ведь его
мне в своё время рекомендовал Henning после вопроса "а чего это Theo
рекомендует код OpenBGPD в качестве замечательного примера, а там
poll() вместо более производительной и эффективной kevent()? и почему
тогда не wide used select()?"
 Тот сказал типа "да вот, такое дело, там не слишком много сокетов (3
вроде), чтобы производительность пострадала от неиспользования
kevent(), а вот select() та ещё зараза имеет ограничения и юзать её
неудобно, поэтому poll()".

И вот я словил, можно сказать, почти все глюки: select() - зло,
kevent() нормально, poll() - то ещё зло :-)

Это можно, конечно, понять. Мало кому надо неблокирующий ввод/вывод на
диск. Отсюда возникает мысль: "наверное моя предыдущая идея с
блокирующим системным вызовом (read) ПРЕРЫВАЕМЫМ по сигналу от alarm()
является более правильной".

За сим благодарю почтеннейшую публику!
--
engineer