我写了一个函数来观察一个文件(给定一个fd)增长到一定的大小,包括超时。我正在使用kqueue()
/ kevent()
等待文件“扩展”但在我收到文件增长的通知后我必须检查文件大小(并将其与所需大小进行比较) )。这似乎很容易,但我无法想出在POSIX中可靠地做到这一点的方法。
注意:如果文件在指定的时间内根本没有增长,则会超时。所以,这不是一个绝对的超时,只是一个超时,文件发生了一些增长。我在OS X上,但这个问题是针对“每个具有kevent()
/ kqueue()
的POSIX”,应该是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
表示“文件还不够大”或{{ 1}} for file已达到所需大小。
显然,只有> 0
在确定文件大小时可靠才有效。我的第一步是用evaluate_fd_size
实现它并将off_t eof_pos = lseek(fd, 0, SEEK_END)
与eof_pos
进行比较。不幸的是,wanted_size
似乎缓存了结果。因此即使lseek
与kevent
一起返回,文件也会增长,结果可能会相同!然后我想切换到NOTE_EXTEND
但found articles that fstat
caches as well。
我尝试过的最后一件事是在fstat
之前使用fsync(fd);
,突然事情开始起作用了。但是:
off_t eof_pos = lseek(fd, 0, SEEK_END);
真正解决了我的问题fsync()
编辑:重播真的很难,但我看到一个fsync()
没有帮助的案例。在fsync()
事件命中用户空间后,文件大小似乎需要(很少)时间。 NOTE_EXTEND
可能只是作为一个足够好的fsync()
,因此大部分时间都有效: - 。
因此,换句话说:如何在不打开/关闭我无法做的文件的情况下可靠地检查POSIX中的文件大小,因为我不知道文件名。另外,我无法保证这会有所帮助
顺便说一下:sleep()
没有克服缓存问题。
编辑2 :我还创建了all in one demo program。如果它在退出之前打印int new_fd = dup(fd); off_t eof_pos = lseek(new_fd, 0, SEEK_END); close(new_fd);
,一切都很顺利。但通常打印Ok, success
表示竞争条件:触发的最后一个kevent的文件大小检查小于此时的实际文件大小。奇怪的是,当使用Timeout (10000000)
来增长文件而不是ftruncate()
时,它似乎有效(您可以使用write()
编译测试程序来测试它。)
答案 0 :(得分:2)
- 没有说fsync()真正解决了我的问题
- 由于性能原因我不想fsync()
醇>
你的问题不是“fstat缓存结果”,而是I / O系统缓冲写入。在内核将I / O缓冲区刷新到底层文件系统之前,Fstat不会更新。
这就是为什么fsync可以解决您的问题,并且您的问题的任何解决方案或多或少都必须与fsync相当。 (这就是开/关解决方案的副作用。)
无法帮助你,因为我没有办法避免做fsync。