我写了一个函数来观察一个文件(给定一个 fd)增长到一定大小,包括超时。我正在使用kqueue()
/kevent()
等待文件“扩展”,但是在收到文件增长的通知后,我必须检查文件大小(并将其与所需大小进行比较)。这似乎很容易,但我无法找到一种在 POSIX 中可靠地做到这一点的方法。
注意:如果文件在指定的时间内完全没有增长,则会超时。因此,这不是绝对超时,只是文件发生一些增长的超时。我在 OS X 上,但这个问题是针对“每个具有kevent()
/的 POSIX 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;
}
}
所以基本算法的工作方式如下:
- 设置 kevent
- 检查尺寸
- 等待文件增长
重复步骤 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()
因为性能
编辑:这真的很难重现,但我看到了一个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
来测试它)。