Логика работы inotify
От: real_sba http://cellwar.xyz/
Дата: 21.11.12 09:26
Оценка:
Доброго времени суток.
Господа, объясните пожалуйста специфику работы с inotify. Признаюсь, задал похожий вопрос в разделе Unix, но пока безрезультатно.
Итак суть вопроса. Несмотря на то, что man гласит:
       Each successful read(2) returns a buffer containing one or more of the following structures:

           struct inotify_event {
               int      wd;       /* Watch descriptor */
               uint32_t mask;     /* Mask of events */
               uint32_t cookie;   /* Unique cookie associating related
                                     events (for rename(2)) */
               uint32_t len;      /* Size of name field */
               char     name[];   /* Optional null-terminated name */
           };

mask contains bits that describe the event that occurred (see below).

мой код упорно получает по одной записи на каждое событие, что весьма странно, так как не соответствует информации в официальном man. Да и весьма неудобно это. Тоесть вместо одной структуры inotify_event с объединением всех событий (даже повторяющихся, что весьма удобно) я получаю длинный список где каждое событие представлено отдельно.
Вопрос: это нормальное поведение inotify? Или можно/нужно настраивать inotify?

Так выглядит, что для обеспечения наблюдения за изменениями файлов в заданном каталоге надо еще дополнительно реализовывать надстройку над inotify, которая бы группировала одинаковые события, чтобы многократно не вызывать обработчик для заданного события.
Re: Логика работы inotify
От: Mazay Россия  
Дата: 21.11.12 09:37
Оценка:
Здравствуйте, real_sba, Вы писали:

_>Господа, объясните пожалуйста специфику работы с inotify. Признаюсь, задал похожий вопрос в разделе Unix, но пока безрезультатно.

_>Итак суть вопроса. Несмотря на то, что man гласит:
_>
_>       Each successful read(2) returns a buffer containing one or more of the following structures:
...
_>

_>мой код упорно получает по одной записи на каждое событие, что весьма странно, так как не соответствует информации в официальном man. Да и весьма неудобно это. Тоесть вместо одной структуры inotify_event с объединением всех событий (даже повторяющихся, что весьма удобно) я получаю длинный список где каждое событие представлено отдельно.
_>Вопрос: это нормальное поведение inotify? Или можно/нужно настраивать inotify?

А покажи код вместе с вызовом read.
Главное гармония ...
Re[2]: Пример кода
От: real_sba http://cellwar.xyz/
Дата: 21.11.12 10:04
Оценка:
Здравствуйте, Mazay, Вы писали:

M>А покажи код вместе с вызовом read.

CDirMonitor::CDirMonitor() :
  m_Inotify(-1)
{ }

CDirMonitor::~CDirMonitor() { }

bool CDirMonitor::Init() {
  m_Inotify = inotify_init1(IN_NONBLOCK);
  if (m_Inotify == -1) {
    LOG_SYSTEM_ERROR;
    return false;
  }

  return true;
}

void CDirMonitor::AddDirectory(const std::string& dirname, uint32_t mask/*, const THandler& handler*/) {
  using namespace boost;

  int wd = inotify_add_watch(m_Inotify, dirname.c_str(), mask);
  if (wd == -1) {
    boost::system::system_error e(
      boost::system::error_code(
        errno,
        boost::system::get_system_category()),
        "CDirMonitor::AddDirectory: inotify_add_watch failed"
    );

    BOOST_THROW_EXCEPTION(e);
  }

  mutex::scoped_lock lock(m_Mutex);
  m_WatchDescriptors.insert(std::make_pair(wd, dirname));
}

void CDirMonitor::RemoveDirectory(const std::string& dirname) { }

bool CDirMonitor::Process() {
  char buf[4096];
  ssize_t len = sizeof(buf);

  while (len == sizeof(buf)) {
    len = read(m_Inotify, buf, sizeof(buf));
    if (len <= 0) {
      break;
    }
    m_ReadBuff += std::string(buf, len);
  }

  if (len < 0 && errno != EAGAIN) {
    LOG_SYSTEM_ERROR;
    return false;
  }

  while (m_ReadBuff.size() > sizeof(inotify_event)) {
    const inotify_event *iev = reinterpret_cast<const inotify_event*>(m_ReadBuff.data());

    TWatchDescriptors::const_iterator it = m_WatchDescriptors.find(iev->wd);
    if (it != m_WatchDescriptors.end()) {
      LOG_DEBUG << it->second << " | " << iev->name << " | " << iev->mask;
    }

    m_ReadBuff.erase(0, sizeof(inotify_event) + iev->len);
  }
}


int main (int argc, char *argv[]) {
  CDirMonitor dm;
  dm.Init();
  dm.AddDirectory("/home/sba/projects/temp", IN_ALL_EVENTS);

  while (1) {
    dm.Process();
    sleep(1);
  }

  return 0;
}
Re[3]: Пример кода
От: Mazay Россия  
Дата: 21.11.12 13:58
Оценка:
Здравствуйте, real_sba, Вы писали:

У меня этот код за раз читает несколько событий. Для теста я удаляю несколько папок в отслеживаемой папке командой rm -r /tmp/test/*. И мне в одном read приходят сразу несколько событий.
Может у тебя события слишком медленно происходят, так что read успевает вызваться несколько раз? Попробуй паузу по-больше поставить.
Главное гармония ...
Re[4]: Пример кода
От: real_sba http://cellwar.xyz/
Дата: 21.11.12 14:53
Оценка:
Здравствуйте, Mazay, Вы писали:

M>У меня этот код за раз читает несколько событий. Для теста я удаляю несколько папок в отслеживаемой папке командой rm -r /tmp/test/*. И мне в одном read приходят сразу несколько событий.

M>Может у тебя события слишком медленно происходят, так что read успевает вызваться несколько раз? Попробуй паузу по-больше поставить.
Я имею ввиду немного другое. Данный код и куча других примеров которые нагуглил и попробовал работают именно так как написано выше.
Не имеет значения как часто происходят события. Код выше расчитан чтобы вычитывать сколько угодно большой read, все сначала быстро складывается в буфер послче чего буфер обрабатывается. Вопрос совсем в другом. Меня крайне удивляет следующее. Допустим я наблюдаю некоторое возможно длительное время за каталогом и тут внезапно решил сделать read. Вместо того чтобы получить по одному inotify_event для каждого каким либо образом измененного файла с битовой маской всех событий (согласно как написано в мане)
mask contains bits that describe the event that occurred (see below).

я получаю каждый event отдельно. И это однозначно не связано с тем что события происходят медленно. Все они получены именно в одном единственном read. Но почемуто даже для одного и того же файла каждое событие отдельно.

Пример:
1. Добавляем наблюдение для каталога /home/sba/projects/temp
2. Открываем и сохраняем в kate файл example.txt
3. Ждем 10 сек и только после этого делаем один read

16:41:08 DEBUG [src/CDirMonitor.cpp:58 - Process] 1024
Как и ожидалось все пришло одним read, один блок длиной 1024 байта
                                                  Каталог                   Файл        Маска  cookie
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 32 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 1 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 16 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 32 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 1 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 16 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 32 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 1 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 16 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 32 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 1 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 16 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt~ | 512 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 32 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | qt_temp.C15277 | 256 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | qt_temp.C15277 | 32 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 1 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | qt_temp.C15277 | 2 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | qt_temp.C15277 | 8 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | qt_temp.C15277 | 64 | 2325
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt~ | 128 | 2325
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt~ | 4 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 16 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 2 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 32 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 2 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 8 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 32 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 16 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 32 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 1 | 0
16:41:08 DEBUG [src/CDirMonitor.cpp:72 - Process] /home/sba/projects/temp | example.txt | 16 | 0


Как видно маска вовсе не contains bits а contains bit.

Для себя хочу понять, действительно ли это нормальное поведение inotify.
И отсюда вопрос, для определения устаревания файлов, таких как удаление, изменение (нужно для кешера) придется поверх inotify сооружать еще контейнер который бы группировал однотипные события в одну записть, чтобы не вызывать лишний раз обработчик события. На примере предоставленом выше пользователь только один раз сохранил файл, значит внутренности поменялись только один раз, и по логике надо только один раз вызвать обработчик который удалит файл из кеша, но inotify уперто подсказывает что IN_MODIFY аж 2 раза. Ладно это неприциппиально, привяжемся только к IN_CLOSE_WRITE который действительно показывается только 1 раз. Но если б надо было цеплять обработчик на любой другой event, то получилась бы каша из ненужный вызовов.
Re[5]: Пример кода
От: vnp  
Дата: 21.11.12 18:27
Оценка: 2 (1)
Здравствуйте, real_sba, Вы писали:

_>Здравствуйте, Mazay, Вы писали:


Вопрос совсем в другом. Меня крайне удивляет следующее. Допустим я наблюдаю некоторое возможно длительное время за каталогом и тут внезапно решил сделать read. Вместо того чтобы получить по одному inotify_event для каждого каким либо образом измененного файла с битовой маской всех событий (согласно как написано в мане)
_>
_>mask contains bits that describe the event that occurred (see below).
_>


_>я получаю каждый event отдельно. И это однозначно не связано с тем что события происходят медленно. Все они получены именно в одном единственном read. Но почемуто даже для одного и того же файла каждое событие отдельно.


_>Как видно маска вовсе не contains bits а contains bit.


Прошу обратить внимание: event стоит в единственном числе. Маска описывает ровно одно событие (точнее, серию идентичных). Упомянутые биты (которые во множественном числе), включают IN_IGNORED и компанию.

_>Для себя хочу понять, действительно ли это нормальное поведение inotify.


Да.

_>И отсюда вопрос, для определения устаревания файлов, таких как удаление, изменение (нужно для кешера) придется поверх inotify сооружать еще контейнер который бы группировал однотипные события в одну записть, чтобы не вызывать лишний раз обработчик события. На примере предоставленом выше пользователь только один раз сохранил файл, значит внутренности поменялись только один раз, и по логике надо только один раз вызвать обработчик который удалит файл из кеша, но inotify уперто подсказывает что IN_MODIFY аж 2 раза. Ладно это неприциппиально, привяжемся только к IN_CLOSE_WRITE который действительно показывается только 1 раз. Но если б надо было цеплять обработчик на любой другой event, то получилась бы каша из ненужный вызовов.


Это особенности редактора. Он зачем-то переоткрывает файл несколько раз. Протестируй на чем-нибудь более простом, например (watch вполне аналогичен твоему коду):


$ ./watch junk &
[1] 21595
$ echo aaa > junk/foo
foo   :    0002    modify
foo   :    0020    open
foo   :    0002    modify
foo   :    0008    close write
$  echo aaa > junk/bar
bar   :    0100    create
bar   :    0020    open
bar   :    0002    modify
bar   :    0008    close write
$ echo aaa >> junk/bar
bar   :    0020    open
bar   :    0002    modify
bar   :    0008    close write
$
Re[5]: Пример кода
От: sunriseq  
Дата: 18.03.16 19:03
Оценка:
Здравствуйте, Mazay , Вы писали:

M>>Может у тебя события слишком медленно происходят, так что read успевает вызваться несколько раз? Попробуй паузу по-больше поставить.


У меня наблюдается именно эта проблема, не могу понять как поставить паузу. Подскажите как это сделать?
Re[3]: Пример кода
От: alexku Россия  
Дата: 21.03.16 16:18
Оценка:
Здравствуйте, real_sba, Вы писали:

  char buf[4096];
  ssize_t len = sizeof(buf);

  while (len == sizeof(buf)) { //<----------- посмотри сюда
    len = read(m_Inotify, buf, sizeof(buf));
    if (len <= 0) {
      break;
    }
    m_ReadBuff += std::string(buf, len); //<---- А это что? У тебя читаются не строки, а структуры inotify_event
  }
}


У тебя твой цикл всегда будет выполняться ровно один раз, потому что read может вернуть размер твоего буфера только случайно.

Тебе надо вычитать структуру в буфер размером sizeof(struct inotify_event), затем в буфер, размером не менее inotify_event::len прочитать inotify_event::name, и только потом добавлять этот name в m_ReadBuff, если я правильно понял, что m_ReadBuff — строка.

И ещё нужно учитывать, что у тебя необязательно сразу прочитается столько, сколько ты попросил. Может быть и меньше. В этом случае нужно продолжать чтение с учётом уже прочитанного. Но и в случае ошибки нельзя сразу выходить. В случае если read(...) == -1 нужно проверять errno на EINTR и на EAGAIN (см. man 2 read) и повторять чтение, если поймана одна из них.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.