macos - 在给定文件描述符的情况下确定 POSIX/OS X 上文件大小的可靠方法

标签 macos caching posix bsd kqueue

我编写了一个函数来观察文件(给定一个 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;
    }
}

所以基本算法的工作方式如下:

  1. 设置 kevent
  2. 检查尺寸
  3. 等待文件增长

重复步骤 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但是found articles that fstat caches as well .

我尝试的最后一件事是使用 fsync(fd);之前off_t eof_pos = lseek(fd, 0, SEEK_END);突然事情开始起作用了。但是:

  1. 没有任何内容表明 fsync()确实解决了我的问题
  2. 我不想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:我还创建了一个 all in one demo program 。如果打印 Ok, success退出之前,一切都很顺利。但通常它会打印 Timeout (10000000)这体现了竞争条件:最后触发的 kevent 的文件大小检查小于此时的实际文件大小。使用 ftruncate() 时很奇怪增大文件而不是 write()它似乎可以工作(您可以使用 -DUSE_FTRUNCATE 编译测试程序来测试它)。

最佳答案

  1. Nothing states that fsync() really solves my problem
  2. I don't want to fsync() because of performance

您的问题不是“fstat 缓存结果”,而是 I/O 系统缓冲写入。在内核将 I/O 缓冲区刷新到底层文件系统之前,Fstat 不会更新。

这就是为什么 fsync 可以解决您的问题,并且任何解决您的问题的方法或多或少都必须执行与 fsync 相同的操作。 (这就是开/关解决方案的副作用。)

无法帮助您解决 2,因为我没有找到任何方法来避免进行 fsync。

关于macos - 在给定文件描述符的情况下确定 POSIX/OS X 上文件大小的可靠方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20475590/

相关文章:

java - hibernate只读查询缓存机制

multithreading - POSIX命名信号量可以同步线程吗?

c - Linux 共享内存同步

ios - Xcode源码路径

java - 如何在 OSX 上的 Java 中获取文件所有者的名称?

c++ - Xcode 4 mach-o 错误

macos - NSButtonCell 中的 NSImage 圆角

database - 什么是缓存?

java - 用于投影的 Hibernate 缓存

在 shell 中用管道连接 n 个命令?