Я написал функцию для наблюдения за тем, как файл (с учетом fd) увеличивается до определенного размера, включая тайм-аут. Я использую kqueue()
/kevent()
, чтобы дождаться «расширения» файла, но после того, как я получаю уведомление о том, что файл увеличился, мне нужно проверить размер файла (и сравнить его с желаемым размером). Это кажется простым, но я не могу найти способ сделать это надежно в POSIX.
NB: тайм-аут сработает, если файл вообще не увеличится в течение указанного времени. Таким образом, это не абсолютный тайм-аут, а просто тайм-аут, когда файл увеличивается. Я работаю в OS X, но этот вопрос предназначен для «каждого POSIX, имеющего kevent()
/kqueue()
», который, я думаю, должен быть OS X и BSD.
Вот моя текущая версия моей функции:
/**
* Blocks until `fd` reaches `size`. Times out if `fd` isn't extended for `timeout`
* amount of time. Returns `-1` and sets `errno` to `EFBIG` should the file be bigger
* than wanted.
*/
int fwait_file_size(int fd,
off_t size,
const struct timespec *restrict timeout)
{
int ret = -1;
int kq = kqueue();
struct kevent changelist[1];
if (kq < 0) {
/* errno set by kqueue */
ret = -1;
goto out;
}
memset(changelist, 0, sizeof(changelist));
EV_SET(&changelist[0], fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_RENAME | NOTE_EXTEND, 0, 0);
if (kevent(kq, changelist, 1, NULL, 0, NULL) < 0) {
/* errno set by kevent */
ret = -1;
goto out;
}
while (true) {
{
/* Step 1: Check the size */
int suc_sz = evaluate_fd_size(fd, size); /* IMPLEMENTATION OF THIS IS THE QUESTION */
if (suc_sz > 0) {
/* wanted size */
ret = 0;
goto out;
} else if (suc_sz < 0) {
/* errno and return code already set */
ret = -1;
goto out;
}
}
{
/* Step 2: Wait for growth */
int suc_kev = kevent(kq, NULL, 0, changelist, 1, timeout);
if (0 == suc_kev) {
/* That's a timeout */
errno = ETIMEDOUT;
ret = -1;
goto out;
} else if (suc_kev > 0) {
if (changelist[0].filter == EVFILT_VNODE) {
if (changelist[0].fflags & NOTE_RENAME || changelist[0].fflags & NOTE_DELETE) {
/* file was deleted, renamed, ... */
errno = ENOENT;
ret = -1;
goto out;
}
}
} else {
/* errno set by kevent */
ret = -1;
goto out;
}
}
}
out: {
int errno_save = errno;
if (kq >= 0) {
close(kq);
}
errno = errno_save;
return ret;
}
}
Таким образом, основной алгоритм работает следующим образом:
- Настроить кевент
- Проверить размер
- Дождитесь роста файла
Шаги 2 и 3 повторяются до тех пор, пока файл не достигнет нужного размера.
В коде используется функция int evaluate_fd_size(int fd, off_t wanted_size)
, которая возвращает < 0
, если "произошла какая-то ошибка или файл больше требуемого", == 0
, если "файл еще недостаточно велик", или > 0
, если файл достиг нужного размера.
Очевидно, это работает только в том случае, если evaluate_fd_size
надежно определяет размер файла. Первым делом я реализовал это с помощью off_t eof_pos = lseek(fd, 0, SEEK_END)
и сравнил eof_pos
с wanted_size
. К сожалению, lseek
, похоже, кэширует результаты. Так что даже когда kevent
вернулся с NOTE_EXTEND
, так что файл вырос, результат может быть тот же! Потом я решил переключиться на fstat
, но обнаружил статьи, которые fstat
тоже кэширует.
Последнее, что я пробовал, это использовать fsync(fd);
перед off_t eof_pos = lseek(fd, 0, SEEK_END);
, и вдруг все заработало. Но:
- Ничто не говорит о том, что
fsync()
действительно решает мою проблему - Я не хочу
fsync()
из-за производительности
EDIT: это действительно сложно воспроизвести, но я видел один случай, когда fsync()
не помогло. Кажется, требуется (очень мало) времени, пока размер файла не станет больше после того, как событие NOTE_EXTEND
попадет в пространство пользователя. fsync()
, вероятно, просто работает как достаточно хороший sleep()
и поэтому работает большую часть времени :-.
Итак, другими словами: как надежно проверить размер файла в POSIX, не открывая/закрывая файл, что я не могу сделать, потому что не знаю имени файла. Кроме того, я не могу найти гарантии, что это поможет
Кстати: int new_fd = dup(fd); off_t eof_pos = lseek(new_fd, 0, SEEK_END); close(new_fd);
проблему с кешированием не поборол.
РЕДАКТИРОВАНИЕ 2: я также создал все в одном демонстрационная программа. Если он печатает Ok, success
перед выходом, все прошло нормально. Но обычно он печатает Timeout (10000000)
, что свидетельствует о состоянии гонки: проверка размера файла для последнего запущенного kevent меньше фактического размера файла в данный момент. Как ни странно, при использовании ftruncate()
для увеличения файла вместо write()
кажется, что это работает (вы можете скомпилировать тестовую программу с -DUSE_FTRUNCATE
, чтобы проверить это).
fchown(fd, -1, -1)
перед вызовом stat ? - person cnicutar   schedule 09.12.2013